From 2a795423d8ad19e6f1997c60885f44231aeb2f78 Mon Sep 17 00:00:00 2001 From: Van Minh Date: Thu, 4 Sep 2025 22:41:28 +0700 Subject: [PATCH 1/2] feat: add transaction progress steps component and documentation --- components/txn-progress-steps-preview.tsx | 207 ++++++++++++++++++ .../Txn-Feedback/txn-progress-steps.tsx | 83 +++++++ components/ui/murphy/index.tsx | 7 +- .../Txn-Feedback/txn-progress-steps.mdx | 159 ++++++++++++++ content/docs/onchainkit/meta.json | 3 +- public/r/txn-progress-steps.json | 16 ++ registry.json | 14 ++ registry/components/txn-progress-steps.json | 14 ++ types/transaction/index.ts | 27 +++ 9 files changed, 526 insertions(+), 4 deletions(-) create mode 100644 components/txn-progress-steps-preview.tsx create mode 100644 components/ui/murphy/Txn-Feedback/txn-progress-steps.tsx create mode 100644 content/docs/onchainkit/Txn-Feedback/txn-progress-steps.mdx create mode 100644 public/r/txn-progress-steps.json create mode 100644 registry/components/txn-progress-steps.json create mode 100644 types/transaction/index.ts diff --git a/components/txn-progress-steps-preview.tsx b/components/txn-progress-steps-preview.tsx new file mode 100644 index 0000000..f635956 --- /dev/null +++ b/components/txn-progress-steps-preview.tsx @@ -0,0 +1,207 @@ +"use client"; + +import { useRef, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { TxnStep } from "@/types/transaction"; +import { TxnProgressSteps } from "./ui/murphy/Txn-Feedback/txn-progress-steps"; + +export default function TxnProgressStepsPreview() { + const initialSteps: TxnStep[] = [ + { + id: "1", + title: "Connect Wallet", + description: "Connect your Solana wallet", + status: "completed", + }, + { + id: "2", + title: "Prepare Transaction", + description: "Preparing transaction data", + status: "completed", + }, + { + id: "3", + title: "Sign Transaction", + description: "Sign with your wallet", + status: "active", + }, + { + id: "4", + title: "Submit to Network", + description: "Broadcasting to Solana", + status: "pending", + }, + { + id: "5", + title: "Confirmation", + description: "Waiting for confirmation", + status: "pending", + }, + ]; + + const [currentSteps, setCurrentSteps] = useState(initialSteps); + const intervalRef = useRef(null); + + const updateSteps = ( + stepIndex: number, + status: TxnStep["status"] + ): TxnStep[] => { + return initialSteps.map((step, index) => { + if (index < stepIndex) return { ...step, status: "completed" }; + if (index === stepIndex) return { ...step, status }; + return { ...step, status: "pending" }; + }); + }; + + const simulateProgress = () => { + if (intervalRef.current) return; // prevent multiple intervals + + let step = 0; + intervalRef.current = setInterval(() => { + if (step < initialSteps.length) { + setCurrentSteps(updateSteps(step, "active")); + step++; + } else { + setCurrentSteps(updateSteps(initialSteps.length - 1, "completed")); + clearStimulate(); + } + }, 1500); + }; + + const simulateError = () => { + const activeIndex = currentSteps.findIndex( + (step) => step.status === "active" + ); + if (activeIndex === -1) return; + + // Stop progression + clearStimulate(); + + // Set error step + setCurrentSteps((prev) => + prev.map((step, index) => + index === activeIndex ? { ...step, status: "error" } : step + ) + ); + }; + + const clearStimulate = () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + }; + + const reset = () => { + clearStimulate(); + setCurrentSteps(initialSteps); + }; + + return ( +
+ {/* Horizontal */} + + + Horizontal Layout + + Perfect for desktop interfaces and wide layouts + + + + +
+ + + +
+
+
+ + {/* Vertical */} + + + Vertical Layout + + Ideal for mobile interfaces and detailed descriptions + + + + + + + + {/* Step States */} + + + Step States + + Different visual states for transaction steps + + + +
+

Success Flow

+ +
+
+

Error Flow

+ +
+
+
+
+ ); +} diff --git a/components/ui/murphy/Txn-Feedback/txn-progress-steps.tsx b/components/ui/murphy/Txn-Feedback/txn-progress-steps.tsx new file mode 100644 index 0000000..db6ecea --- /dev/null +++ b/components/ui/murphy/Txn-Feedback/txn-progress-steps.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { Check, Loader2, X } from "lucide-react"; +import { cn } from "@/lib/utils"; +import type { TxnStep } from "@/types/transaction"; + +interface TxnProgressStepsProps { + steps: TxnStep[]; + orientation?: "horizontal" | "vertical"; + className?: string; +} + +export function TxnProgressSteps({ + steps, + orientation = "horizontal", + className, +}: TxnProgressStepsProps) { + const isVertical = orientation === "vertical"; + + return ( +
+ {steps.map((step, index) => ( +
+
+ {step.status === "completed" && } + {step.status === "active" && ( + + )} + {step.status === "error" && } + {step.status === "pending" && ( + {index + 1} + )} +
+ +
+
+ {step.title} +
+ {step.description && ( +
+ {step.description} +
+ )} +
+ + {!isVertical && index < steps.length - 1 && ( +
+ )} +
+ ))} +
+ ); +} diff --git a/components/ui/murphy/index.tsx b/components/ui/murphy/index.tsx index 987b191..bdd64de 100644 --- a/components/ui/murphy/index.tsx +++ b/components/ui/murphy/index.tsx @@ -14,7 +14,7 @@ import CandyMachineForm from "./candy-machine-form"; import CoreCandyMachineForm from "./core-candy-machine-form"; import BubblegumLegacyForm from "./bubblegum-legacy-form"; import ImprovedCNFTManager from "./improved-cnft-manager"; -import CompressedNFTViewer from "./compressed-nft-viewer" +import CompressedNFTViewer from "./compressed-nft-viewer"; import { CreateMerkleTree } from "./create-merkleTree-form"; import { TokenList } from "./token-list"; import { StakeForm } from "./stake-token-form"; @@ -37,7 +37,7 @@ import { CoreAssetLaunchpad } from "./core-asset-launchpad"; import { HydraFanoutForm } from "./hydra-fanout-form"; import { MPLHybridForm } from "./mpl-hybrid-form"; import { TokenMetadataViewer } from "./token-metadata-viewer"; - +import { TxnProgressSteps } from "@/components/ui/murphy/Txn-Feedback/txn-progress-steps"; export { ConnectWalletButton, SendTokenForm, @@ -78,5 +78,6 @@ export { TMLaunchpadForm, HydraFanoutForm, MPLHybridForm, - TokenMetadataViewer + TokenMetadataViewer, + TxnProgressSteps, }; diff --git a/content/docs/onchainkit/Txn-Feedback/txn-progress-steps.mdx b/content/docs/onchainkit/Txn-Feedback/txn-progress-steps.mdx new file mode 100644 index 0000000..96276a2 --- /dev/null +++ b/content/docs/onchainkit/Txn-Feedback/txn-progress-steps.mdx @@ -0,0 +1,159 @@ +--- +title: Transaction Progress Steps +description: Visual progress indicator for multi-step transaction workflows +icon: Activity +--- + +import TxnProgressStepsPreview from "@/components/txn-progress-steps-preview"; + + +
+
+ +
+
+
+ +## Installation + + + + + Install Transaction Progress Steps + + + + + +## Basic Usage + +```tsx +"use client"; + +import { TxProgressSteps } from "@/components/ui/murphy/Txn-Feedback/txn-progress-steps"; +import type { TxStep } from "@/types/transaction"; + +export default function MyPage() { + const steps: TxStep[] = [ + { + id: "1", + title: "Connect Wallet", + description: "Connect your Solana wallet", + status: "completed", + }, + { + id: "2", + title: "Prepare Transaction", + description: "Preparing transaction data", + status: "completed", + }, + { + id: "3", + title: "Sign Transaction", + description: "Sign with your wallet", + status: "active", + }, + { + id: "4", + title: "Submit to Network", + description: "Broadcasting to Solana", + status: "pending", + }, + { + id: "5", + title: "Confirmation", + description: "Waiting for confirmation", + status: "pending", + }, + ]; + + return ( +
+

Transaction Progress

+ +
+ ); +} +``` + +## Props + + + +### TxStep Interface + + + +## Customization + +### Custom Styling + +```tsx + +``` + +### Responsive Design + +```tsx + + + +``` diff --git a/content/docs/onchainkit/meta.json b/content/docs/onchainkit/meta.json index d2b1c3b..329e6c2 100644 --- a/content/docs/onchainkit/meta.json +++ b/content/docs/onchainkit/meta.json @@ -31,6 +31,7 @@ "Metaplex", "Meteora-DBC", "ZK-Compression", - "Jupiter-Recurring" + "Jupiter-Recurring", + "Txn-Feedback" ] } diff --git a/public/r/txn-progress-steps.json b/public/r/txn-progress-steps.json new file mode 100644 index 0000000..a72009d --- /dev/null +++ b/public/r/txn-progress-steps.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "txn-progress-steps", + "type": "registry:block", + "title": "A visual step tracker for multi-step transaction workflows.", + "dependencies": [], + "registryDependencies": [], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/txn-progress-steps.tsx", + "content": "\"use client\";\r\n\r\nimport { Check, Loader2, X } from \"lucide-react\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport type { TxnStep } from \"@/types/transaction\";\r\n\r\ninterface TxnProgressStepsProps {\r\n steps: TxnStep[];\r\n orientation?: \"horizontal\" | \"vertical\";\r\n className?: string;\r\n}\r\n\r\nexport function TxnProgressSteps({\r\n steps,\r\n orientation = \"horizontal\",\r\n className,\r\n}: TxnProgressStepsProps) {\r\n const isVertical = orientation === \"vertical\";\r\n\r\n return (\r\n \r\n {steps.map((step, index) => (\r\n \r\n \r\n {step.status === \"completed\" && }\r\n {step.status === \"active\" && (\r\n \r\n )}\r\n {step.status === \"error\" && }\r\n {step.status === \"pending\" && (\r\n {index + 1}\r\n )}\r\n
\r\n\r\n
\r\n \r\n {step.title}\r\n
\r\n {step.description && (\r\n
\r\n {step.description}\r\n
\r\n )}\r\n \r\n\r\n {!isVertical && index < steps.length - 1 && (\r\n
\r\n )}\r\n
\r\n ))}\r\n \r\n );\r\n}\r\n", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/txn-progress-steps.tsx" + } + ] +} \ No newline at end of file diff --git a/registry.json b/registry.json index bac943b..dbb1793 100644 --- a/registry.json +++ b/registry.json @@ -1412,6 +1412,20 @@ } ] }, + { + "name": "txn-progress-steps", + "type": "registry:block", + "title": "A visual step tracker for multi-step transaction workflows.", + "registryDependencies": [], + "dependencies": [], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/txn-progress-steps.tsx", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/txn-progress-steps.tsx" + } + ] + }, { "name": "txn-settings", "type": "registry:block", diff --git a/registry/components/txn-progress-steps.json b/registry/components/txn-progress-steps.json new file mode 100644 index 0000000..fdec8cb --- /dev/null +++ b/registry/components/txn-progress-steps.json @@ -0,0 +1,14 @@ +{ + "name": "txn-progress-steps", + "type": "registry:block", + "title": "A visual step tracker for multi-step transaction workflows.", + "registryDependencies": [], + "dependencies": [], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/txn-progress-steps.tsx", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/txn-progress-steps.tsx" + } + ] +} diff --git a/types/transaction/index.ts b/types/transaction/index.ts new file mode 100644 index 0000000..5be4a81 --- /dev/null +++ b/types/transaction/index.ts @@ -0,0 +1,27 @@ +export interface TransactionStatus { + status: + | "idle" + | "preparing" + | "signing" + | "sending" + | "confirming" + | "success" + | "error"; + signature?: string; + error?: string; + step?: number; + totalSteps?: number; +} + +export interface TxnStep { + id: string; + title: string; + description?: string; + status: "pending" | "active" | "completed" | "error"; +} + +export interface TxnFeedbackProps { + status: TransactionStatus; + onRetry?: () => void; + onClose?: () => void; +} From 51738a8f95228db60678c1208964e031a3d9f06a Mon Sep 17 00:00:00 2001 From: Van Minh Date: Thu, 4 Sep 2025 23:07:59 +0700 Subject: [PATCH 2/2] feat: add Step Flow Dialog component and documentation for multi-step workflows --- components/step-flow-dialog-preview.tsx | 195 +++++++++++++++ .../murphy/Txn-Feedback/step-flow-dialog.tsx | 146 +++++++++++ components/ui/murphy/index.tsx | 3 + .../Txn-Feedback/step-flow-dialog.mdx | 230 ++++++++++++++++++ public/r/step-flow-dialog.json | 20 ++ registry.json | 18 ++ registry/components/step-flow-dialog.json | 14 ++ 7 files changed, 626 insertions(+) create mode 100644 components/step-flow-dialog-preview.tsx create mode 100644 components/ui/murphy/Txn-Feedback/step-flow-dialog.tsx create mode 100644 content/docs/onchainkit/Txn-Feedback/step-flow-dialog.mdx create mode 100644 public/r/step-flow-dialog.json create mode 100644 registry/components/step-flow-dialog.json diff --git a/components/step-flow-dialog-preview.tsx b/components/step-flow-dialog-preview.tsx new file mode 100644 index 0000000..0a2b5e7 --- /dev/null +++ b/components/step-flow-dialog-preview.tsx @@ -0,0 +1,195 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { TxnStep } from "@/types/transaction"; +import { StepFlowDialog } from "./ui/murphy"; + +export default function StepFlowDialogPreview() { + const [showDialog, setShowDialog] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const [dialogType, setDialogType] = useState< + "nft-mint" | "token-swap" | "staking" + >("nft-mint"); + + const workflows = { + "nft-mint": { + title: "Mint NFT Process", + description: "Complete the following steps to mint your NFT", + steps: [ + { + id: "1", + title: "Upload Metadata", + description: "Upload image and metadata to IPFS", + status: "pending" as const, + }, + { + id: "2", + title: "Create Mint Account", + description: "Create NFT mint account on Solana", + status: "pending" as const, + }, + { + id: "3", + title: "Mint Token", + description: "Mint NFT to your wallet", + status: "pending" as const, + }, + { + id: "4", + title: "Verify Ownership", + description: "Verify NFT in your wallet", + status: "pending" as const, + }, + ], + }, + "token-swap": { + title: "Token Swap Process", + description: "Swap your tokens through the following steps", + steps: [ + { + id: "1", + title: "Get Quote", + description: "Calculate swap rates and fees", + status: "pending" as const, + }, + { + id: "2", + title: "Approve Spending", + description: "Approve token spending limit", + status: "pending" as const, + }, + { + id: "3", + title: "Execute Swap", + description: "Perform the token exchange", + status: "pending" as const, + }, + ], + }, + staking: { + title: "Staking Process", + description: "Stake your tokens to earn rewards", + steps: [ + { + id: "1", + title: "Select Validator", + description: "Choose a validator to stake with", + status: "pending" as const, + }, + { + id: "2", + title: "Create Stake Account", + description: "Create a new stake account", + status: "pending" as const, + }, + { + id: "3", + title: "Delegate Stake", + description: "Delegate tokens to validator", + status: "pending" as const, + }, + { + id: "4", + title: "Activate Stake", + description: "Wait for stake activation", + status: "pending" as const, + }, + ], + }, + }; + + const updateStepStatus = (stepIndex: number, status: TxnStep["status"]) => { + const currentWorkflow = workflows[dialogType]; + return currentWorkflow.steps.map((step, index) => { + if (index < stepIndex) return { ...step, status: "completed" as const }; + if (index === stepIndex) return { ...step, status }; + return { ...step, status: "pending" as const }; + }); + }; + + const [currentSteps, setCurrentSteps] = useState( + workflows["nft-mint"].steps + ); + + const openDialog = (type: keyof typeof workflows) => { + setDialogType(type); + setCurrentStep(0); + setCurrentSteps(workflows[type].steps); + setShowDialog(true); + }; + + const handleNext = async () => { + await new Promise((resolve) => setTimeout(resolve, 1500)); + + const nextStep = currentStep + 1; + setCurrentSteps(updateStepStatus(currentStep, "completed")); + + if (nextStep < workflows[dialogType].steps.length) { + setCurrentStep(nextStep); + setCurrentSteps(updateStepStatus(nextStep, "active")); + } + }; + + const handlePrevious = () => { + const prevStep = Math.max(currentStep - 1, 0); + setCurrentStep(prevStep); + setCurrentSteps(updateStepStatus(prevStep, "active")); + }; + + const handleCancel = () => { + setShowDialog(false); + setCurrentStep(0); + setCurrentSteps(workflows[dialogType].steps); + }; + + return ( +
+
+

+ Choose a workflow to simulate +

+
+ +
+ + + + + +
+ + 0} + canGoNext={currentStep < workflows[dialogType].steps.length - 1} + /> +
+ ); +} diff --git a/components/ui/murphy/Txn-Feedback/step-flow-dialog.tsx b/components/ui/murphy/Txn-Feedback/step-flow-dialog.tsx new file mode 100644 index 0000000..32467ea --- /dev/null +++ b/components/ui/murphy/Txn-Feedback/step-flow-dialog.tsx @@ -0,0 +1,146 @@ +"use client"; + +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { TxnProgressSteps } from "./txn-progress-steps"; +import { TxnStep } from "@/types/transaction"; + +interface StepFlowDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + title: string; + description?: string; + steps: TxnStep[]; + currentStep: number; + onNext?: () => Promise | void; + onPrevious?: () => void; + onCancel?: () => void; + nextButtonText?: string; + previousButtonText?: string; + cancelButtonText?: string; + isLoading?: boolean; + canGoNext?: boolean; + canGoPrevious?: boolean; + showCancel?: boolean; +} + +export function StepFlowDialog({ + open, + onOpenChange, + title, + description, + steps, + currentStep, + onNext, + onPrevious, + onCancel, + nextButtonText = "Next", + previousButtonText = "Previous", + cancelButtonText = "Cancel", + isLoading = false, + canGoNext = true, + canGoPrevious = true, + showCancel = true, +}: StepFlowDialogProps) { + const [isProcessing, setIsProcessing] = useState(false); + + const handleNext = async () => { + if (!onNext || isProcessing) return; + setIsProcessing(true); + try { + await onNext(); + } catch (error) { + console.error("Step failed:", error); + } finally { + setIsProcessing(false); + } + }; + + const isLastStep = currentStep === steps.length - 1; + const isFirstStep = currentStep === 0; + + return ( + + + + {title} + {description && {description}} + + +
+ + + {steps[currentStep] && ( +
+

+ {steps[currentStep].title} +

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

+ {steps[currentStep].description} +

+ )} +
+ )} +
+ +
+
+ {showCancel && ( + + )} + {!isFirstStep && canGoPrevious && ( + + )} +
+ +
+ {!isLastStep && ( + + )} + {isLastStep && ( + + )} +
+
+
+
+ ); +} diff --git a/components/ui/murphy/index.tsx b/components/ui/murphy/index.tsx index bdd64de..cb71b43 100644 --- a/components/ui/murphy/index.tsx +++ b/components/ui/murphy/index.tsx @@ -38,6 +38,8 @@ import { HydraFanoutForm } from "./hydra-fanout-form"; import { MPLHybridForm } from "./mpl-hybrid-form"; import { TokenMetadataViewer } from "./token-metadata-viewer"; import { TxnProgressSteps } from "@/components/ui/murphy/Txn-Feedback/txn-progress-steps"; +import { StepFlowDialog } from "@/components/ui/murphy/Txn-Feedback/step-flow-dialog"; + export { ConnectWalletButton, SendTokenForm, @@ -80,4 +82,5 @@ export { MPLHybridForm, TokenMetadataViewer, TxnProgressSteps, + StepFlowDialog, }; diff --git a/content/docs/onchainkit/Txn-Feedback/step-flow-dialog.mdx b/content/docs/onchainkit/Txn-Feedback/step-flow-dialog.mdx new file mode 100644 index 0000000..e974c57 --- /dev/null +++ b/content/docs/onchainkit/Txn-Feedback/step-flow-dialog.mdx @@ -0,0 +1,230 @@ +--- +title: Step Flow Dialog +description: Modal for complex multi-step transaction workflows +icon: Workflow +--- + +import StepFlowDialogPreview from "@/components/step-flow-dialog-preview"; + + + + + +## Installation + + + + Install Step Flow Dialog + + + + + + +## Basic Usage + +```tsx +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { StepFlowDialog } from "@/components/ui/murphy/step-flow-dialog"; + +export default function StepFlowDemo() { + const [open, setOpen] = useState(false); + const [step, setStep] = useState(0); + + const steps = [ + { + id: "1", + title: "Step One", + description: "Do something", + status: "pending", + }, + { + id: "2", + title: "Step Two", + description: "Do next thing", + status: "pending", + }, + { + id: "3", + title: "Step Three", + description: "Finish it", + status: "pending", + }, + ]; + + return ( + <> + + + setStep((prev) => prev + 1)} + onPrevious={() => setStep((prev) => prev - 1)} + onCancel={() => setOpen(false)} + canGoPrevious={step > 0} + canGoNext={step < steps.length - 1} + /> + + ); +} +``` + +## Props + + void", + default: "required", + }, + title: { + description: "Dialog title", + type: "string", + default: "required", + }, + description: { + description: "Dialog description", + type: "string", + default: "undefined", + }, + steps: { + description: "Array of steps in the workflow", + type: "TxnStep[]", + default: "required", + }, + currentStep: { + description: "Index of the current active step", + type: "number", + default: "required", + }, + onNext: { + description: "Callback when next button is clicked", + type: "() => Promise | void", + default: "undefined", + }, + onPrevious: { + description: "Callback when previous button is clicked", + type: "() => void", + default: "undefined", + }, + onCancel: { + description: "Callback when cancel button is clicked", + type: "() => void", + default: "undefined", + }, + nextButtonText: { + description: "Text for the next button", + type: "string", + default: "'Next'", + }, + previousButtonText: { + description: "Text for the previous button", + type: "string", + default: "'Previous'", + }, + cancelButtonText: { + description: "Text for the cancel button", + type: "string", + default: "'Cancel'", + }, + isLoading: { + description: "Whether the dialog is in loading state", + type: "boolean", + default: "false", + }, + canGoNext: { + description: "Whether the next button should be enabled", + type: "boolean", + default: "true", + }, + canGoPrevious: { + description: "Whether the previous button should be enabled", + type: "boolean", + default: "true", + }, + showCancel: { + description: "Whether to show the cancel button", + type: "boolean", + default: "true", + }, + }} +/> + +## Dialog Features + +### Visual Progress Tracking + +- Step-by-step progress visualization +- Current step highlighting +- Completed step indicators +- Error state visualization + +### Navigation Controls + +- Next/Previous button navigation +- Cancel functionality +- Conditional button states +- Loading state management + +### Step Information + +- Step titles and descriptions +- Current step details +- Progress indicators +- Status-based styling + +### Async Operation Support + +- Loading states during processing +- Error handling and retry +- Step validation +- Progress persistence + +## Customization + +### Custom Button Labels + +```tsx + +``` + +### Custom Styling + +```tsx + +``` + +### Conditional Navigation + +```tsx + 0 && !isLoading} + showCancel={!isProcessing} + // ... other props +/> +``` diff --git a/public/r/step-flow-dialog.json b/public/r/step-flow-dialog.json new file mode 100644 index 0000000..a9ef40e --- /dev/null +++ b/public/r/step-flow-dialog.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "step-flow-dialog", + "type": "registry:block", + "title": "Modal for guiding users through multi-step flows (e.g. Mint → Verify → Confirm).", + "dependencies": [], + "registryDependencies": [ + "dialog", + "button", + "txn-progress-steps" + ], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/step-flow-dialog.tsx", + "content": "\"use client\";\r\n\r\nimport { useState } from \"react\";\r\nimport {\r\n Dialog,\r\n DialogContent,\r\n DialogDescription,\r\n DialogHeader,\r\n DialogTitle,\r\n} from \"@/components/ui/dialog\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { TxnProgressSteps } from \"./txn-progress-steps\";\r\nimport { TxnStep } from \"@/types/transaction\";\r\n\r\ninterface StepFlowDialogProps {\r\n open: boolean;\r\n onOpenChange: (open: boolean) => void;\r\n title: string;\r\n description?: string;\r\n steps: TxnStep[];\r\n currentStep: number;\r\n onNext?: () => Promise | void;\r\n onPrevious?: () => void;\r\n onCancel?: () => void;\r\n nextButtonText?: string;\r\n previousButtonText?: string;\r\n cancelButtonText?: string;\r\n isLoading?: boolean;\r\n canGoNext?: boolean;\r\n canGoPrevious?: boolean;\r\n showCancel?: boolean;\r\n}\r\n\r\nexport function StepFlowDialog({\r\n open,\r\n onOpenChange,\r\n title,\r\n description,\r\n steps,\r\n currentStep,\r\n onNext,\r\n onPrevious,\r\n onCancel,\r\n nextButtonText = \"Next\",\r\n previousButtonText = \"Previous\",\r\n cancelButtonText = \"Cancel\",\r\n isLoading = false,\r\n canGoNext = true,\r\n canGoPrevious = true,\r\n showCancel = true,\r\n}: StepFlowDialogProps) {\r\n const [isProcessing, setIsProcessing] = useState(false);\r\n\r\n const handleNext = async () => {\r\n if (!onNext || isProcessing) return;\r\n setIsProcessing(true);\r\n try {\r\n await onNext();\r\n } catch (error) {\r\n console.error(\"Step failed:\", error);\r\n } finally {\r\n setIsProcessing(false);\r\n }\r\n };\r\n\r\n const isLastStep = currentStep === steps.length - 1;\r\n const isFirstStep = currentStep === 0;\r\n\r\n return (\r\n \r\n \r\n \r\n {title}\r\n {description && {description}}\r\n \r\n\r\n
\r\n \r\n\r\n {steps[currentStep] && (\r\n
\r\n

\r\n {steps[currentStep].title}\r\n

\r\n {steps[currentStep].description && (\r\n

\r\n {steps[currentStep].description}\r\n

\r\n )}\r\n
\r\n )}\r\n
\r\n\r\n
\r\n
\r\n {showCancel && (\r\n \r\n {cancelButtonText}\r\n \r\n )}\r\n {!isFirstStep && canGoPrevious && (\r\n \r\n {previousButtonText}\r\n \r\n )}\r\n
\r\n\r\n
\r\n {!isLastStep && (\r\n \r\n {isProcessing || isLoading ? \"Processing...\" : nextButtonText}\r\n \r\n )}\r\n {isLastStep && (\r\n onOpenChange(false)}\r\n disabled={isProcessing || isLoading}\r\n >\r\n Done\r\n \r\n )}\r\n
\r\n
\r\n
\r\n
\r\n );\r\n}\r\n", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/step-flow-dialog.tsx" + } + ] +} \ No newline at end of file diff --git a/registry.json b/registry.json index dbb1793..059aeae 100644 --- a/registry.json +++ b/registry.json @@ -1036,6 +1036,24 @@ } ] }, + { + "name": "step-flow-dialog", + "type": "registry:block", + "title": "Modal for guiding users through multi-step flows (e.g. Mint → Verify → Confirm).", + "registryDependencies": [ + "dialog", + "button", + "txn-progress-steps" + ], + "dependencies": [], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/step-flow-dialog.tsx", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/step-flow-dialog.tsx" + } + ] + }, { "name": "swap-token-form", "type": "registry:block", diff --git a/registry/components/step-flow-dialog.json b/registry/components/step-flow-dialog.json new file mode 100644 index 0000000..641d9d7 --- /dev/null +++ b/registry/components/step-flow-dialog.json @@ -0,0 +1,14 @@ +{ + "name": "step-flow-dialog", + "type": "registry:block", + "title": "Modal for guiding users through multi-step flows (e.g. Mint → Verify → Confirm).", + "registryDependencies": ["dialog", "button", "txn-progress-steps"], + "dependencies": [], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/step-flow-dialog.tsx", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/step-flow-dialog.tsx" + } + ] +}