Skip to content

Commit 650e899

Browse files
feat: signing modal (#294)
1 parent f15a4ea commit 650e899

File tree

13 files changed

+490
-13
lines changed

13 files changed

+490
-13
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Dialog, type DialogProps, MobileDialog } from "./";
2+
import { useIsMobile } from "@/hooks";
3+
import { twMerge } from "tailwind-merge";
4+
5+
/**
6+
* ResponsiveDialog - Automatically switches between Desktop and Mobile dialogs
7+
* based on viewport size.
8+
*
9+
* @param props - All standard Dialog props are supported
10+
* @param breakpoint - Optional custom breakpoint (default: 640px for sm:)
11+
*
12+
* @example
13+
* ```tsx
14+
* <ResponsiveDialog open={isOpen} onClose={handleClose}>
15+
* <DialogHeader title="My Modal" />
16+
* <DialogBody>Content here</DialogBody>
17+
* </ResponsiveDialog>
18+
* ```
19+
*/
20+
export function ResponsiveDialog({
21+
className,
22+
breakpoint = 640,
23+
...restProps
24+
}: DialogProps & { breakpoint?: number }) {
25+
const isMobile = useIsMobile(breakpoint);
26+
const DialogComponent = isMobile ? MobileDialog : Dialog;
27+
28+
return (
29+
<DialogComponent
30+
{...restProps}
31+
className={twMerge("w-[41.25rem] max-w-full", className)}
32+
/>
33+
);
34+
}
35+

packages/babylon-core-ui/src/components/Dialog/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import "./index.css";
22

33
export * from "./Dialog";
44
export * from "./MobileDialog";
5+
export * from "./ResponsiveDialog";
56
export * from "./components/DialogHeader";
67
export * from "./components/DialogBody";
78
export * from "./components/DialogFooter";
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { Step } from "./Step";
3+
4+
const meta: Meta<typeof Step> = {
5+
title: "Components/Step",
6+
component: Step,
7+
parameters: {
8+
layout: "centered",
9+
},
10+
tags: ["autodocs"],
11+
argTypes: {
12+
step: {
13+
control: { type: "number", min: 1, max: 10 },
14+
description: "Step number",
15+
},
16+
currentStep: {
17+
control: { type: "number", min: 1, max: 10 },
18+
description: "Currently active step number",
19+
},
20+
children: {
21+
control: "text",
22+
description: "Step label/description",
23+
},
24+
},
25+
};
26+
27+
export default meta;
28+
type Story = StoryObj<typeof meta>;
29+
30+
/**
31+
* A completed step shows a checkmark icon
32+
*/
33+
export const Completed: Story = {
34+
args: {
35+
step: 1,
36+
currentStep: 3,
37+
children: "This step is complete",
38+
},
39+
};
40+
41+
/**
42+
* The active step shows a spinner animation
43+
*/
44+
export const Active: Story = {
45+
args: {
46+
step: 2,
47+
currentStep: 2,
48+
children: "This step is currently active",
49+
},
50+
};
51+
52+
/**
53+
* A pending step shows the step number
54+
*/
55+
export const Pending: Story = {
56+
args: {
57+
step: 3,
58+
currentStep: 1,
59+
children: "This step is pending",
60+
},
61+
};
62+
63+
/**
64+
* Example of a 3-step process
65+
*/
66+
export const MultiStepProcess: Story = {
67+
render: () => (
68+
<div className="flex flex-col gap-4 w-[500px]">
69+
<Step step={1} currentStep={2}>
70+
Sign Transaction
71+
</Step>
72+
<Step step={2} currentStep={2}>
73+
Broadcast to Network
74+
</Step>
75+
<Step step={3} currentStep={2}>
76+
Confirm on Chain
77+
</Step>
78+
</div>
79+
),
80+
};
81+
82+
/**
83+
* All steps completed
84+
*/
85+
export const AllCompleted: Story = {
86+
render: () => (
87+
<div className="flex flex-col gap-4 w-[500px]">
88+
<Step step={1} currentStep={4}>
89+
Sign Transaction
90+
</Step>
91+
<Step step={2} currentStep={4}>
92+
Broadcast to Network
93+
</Step>
94+
<Step step={3} currentStep={4}>
95+
Confirm on Chain
96+
</Step>
97+
</div>
98+
),
99+
};
100+
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Loader } from "@/components/Loader";
2+
import { Text } from "@/components/Text";
3+
import { type ReactNode } from "react";
4+
import { IoCheckmarkSharp } from "react-icons/io5";
5+
import { twMerge } from "tailwind-merge";
6+
7+
export interface StepProps {
8+
/** Step number (1, 2, 3, etc.) */
9+
step: number;
10+
/** Currently active step number */
11+
currentStep: number;
12+
/** Step label/description */
13+
children: ReactNode;
14+
/** Optional custom className */
15+
className?: string;
16+
}
17+
18+
/**
19+
* renderIcon - Returns appropriate icon based on step state
20+
* - Complete (past): Green checkmark
21+
* - Active (current): Orange spinner
22+
* - Pending (future): Gray number badge
23+
*/
24+
const renderIcon = (step: number, currentStep: number) => {
25+
// Complete: checkmark icon with green background
26+
if (currentStep > step) {
27+
return (
28+
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary-light">
29+
<IoCheckmarkSharp size={24} className="text-accent-contrast" />
30+
</div>
31+
);
32+
}
33+
34+
// Active: spinner with orange background
35+
if (currentStep === step) {
36+
return (
37+
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-secondary-main">
38+
<Loader size={24} className="text-accent-contrast" />
39+
</div>
40+
);
41+
}
42+
43+
// Pending: number badge with gray background
44+
return (
45+
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-secondary-main">
46+
<Text variant="body1" className="text-accent-contrast">
47+
{step}
48+
</Text>
49+
</div>
50+
);
51+
};
52+
53+
/**
54+
* Step Component - Displays a single step in a multi-step process
55+
*
56+
* Shows different visual states based on progress:
57+
* - **Pending** (future): Number badge, reduced opacity
58+
* - **Active** (current): Spinner animation, full opacity
59+
* - **Complete** (past): Checkmark, full opacity
60+
*
61+
* @example
62+
* ```tsx
63+
* <Step step={1} currentStep={2}>
64+
* Complete Step
65+
* </Step>
66+
*
67+
* <Step step={2} currentStep={2}>
68+
* Active Step (spinner)
69+
* </Step>
70+
*
71+
* <Step step={3} currentStep={2}>
72+
* Pending Step
73+
* </Step>
74+
* ```
75+
*/
76+
export function Step({ step, currentStep, children, className }: StepProps) {
77+
const isActive = step === currentStep;
78+
79+
return (
80+
<div
81+
className={twMerge(
82+
"flex w-full flex-row items-center gap-3 self-stretch rounded border border-secondary-strokeLight bg-surface p-4",
83+
!isActive && "opacity-25",
84+
className
85+
)}
86+
>
87+
{renderIcon(step, currentStep)}
88+
<Text variant="body1" className="text-accent-primary">
89+
Step {step}: {children}
90+
</Text>
91+
</div>
92+
);
93+
}
94+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./Step";
2+

packages/babylon-core-ui/src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export * from "./components/Copy";
2727
export * from "./components/Icons";
2828
export * from "./components/Warning";
2929
export * from "./components/Hint";
30+
export * from "./components/Step";
3031

3132
export * from "./elements/FinalityProviderLogo";
3233
export * from "./elements/FinalityProviderItem";
34.1 KB
Loading

routes/vault/src/assets/usdc.svg

Lines changed: 0 additions & 9 deletions
Loading

routes/vault/src/components/modals/BorrowModal.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,15 @@ function ResponsiveDialog({ className, ...restProps }: ResponsiveDialogProps) {
3333
interface BorrowModalProps {
3434
open: boolean;
3535
onClose: () => void;
36+
onBorrow?: (amount: number) => void;
3637
collateral: {
3738
amount: string;
3839
symbol: string;
3940
icon?: string | ReactNode;
4041
};
4142
}
4243

43-
export function BorrowModal({ open, onClose, collateral }: BorrowModalProps) {
44+
export function BorrowModal({ open, onClose, onBorrow, collateral }: BorrowModalProps) {
4445
const collateralBTC = useMemo(
4546
() => parseFloat(collateral.amount || "0"),
4647
[collateral.amount]
@@ -90,8 +91,14 @@ export function BorrowModal({ open, onClose, collateral }: BorrowModalProps) {
9091
const handleBorrowClick = async () => {
9192
setTouched(true);
9293
if (validation.isValid && borrowAmountNum > 0) {
93-
await handleBorrow(borrowAmountNum, collateralBTC);
94-
onClose();
94+
// Call parent callback to trigger sign modal flow
95+
if (onBorrow) {
96+
onBorrow(borrowAmountNum);
97+
} else {
98+
// Fallback: if no onBorrow callback, use old flow
99+
await handleBorrow(borrowAmountNum, collateralBTC);
100+
onClose();
101+
}
95102
}
96103
};
97104

0 commit comments

Comments
 (0)