Skip to content

Commit cafe5ef

Browse files
feat(packages): wallet menu component (#46)
* feat(packages): wallet menu component, icon components and util components
1 parent 5c5af0e commit cafe5ef

30 files changed

+987
-14
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { forwardRef } from "react";
2+
import { Button, type ButtonProps } from "./Button";
3+
import { twJoin } from "tailwind-merge";
4+
5+
export interface WalletDisconnectButtonProps extends Omit<ButtonProps, "color" | "variant"> {
6+
className?: string;
7+
}
8+
9+
export const WalletDisconnectButton = forwardRef<HTMLButtonElement, WalletDisconnectButtonProps>(
10+
({ className, ...props }, ref) => {
11+
return (
12+
<Button
13+
{...props}
14+
ref={ref}
15+
variant="contained"
16+
className={twJoin(
17+
"!bg-error-main text-white font-medium !text-sm hover:!bg-error-main/90 transition-colors",
18+
className
19+
)}
20+
/>
21+
);
22+
}
23+
);
24+
25+
WalletDisconnectButton.displayName = "WalletDisconnectButton";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./Button";
22
export * from "./IconButton";
3+
export * from "./WalletDisconnectButton";
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import React, { useId } from 'react';
2+
import { useCopy, UseCopyOptions } from '../../hooks/useCopy';
3+
import { twMerge } from 'tailwind-merge';
4+
5+
export interface CopyProps {
6+
children: React.ReactNode;
7+
value: string;
8+
showIcon?: boolean;
9+
className?: string;
10+
onCopy?: (value: string) => void;
11+
// External control props
12+
isCopied?: boolean;
13+
copiedText?: string;
14+
// useCopy hook options
15+
timeout?: number;
16+
}
17+
18+
export const Copy: React.FC<CopyProps> = ({
19+
children,
20+
value,
21+
className,
22+
onCopy,
23+
isCopied: externalIsCopied,
24+
copiedText: externalCopiedText,
25+
timeout = 2000,
26+
}) => {
27+
const uniqueId = useId();
28+
29+
const hookOptions: UseCopyOptions = {
30+
copiedText: externalCopiedText,
31+
timeout,
32+
};
33+
34+
const {
35+
isCopied: hookIsCopied,
36+
copiedText: hookCopiedText,
37+
copyToClipboard,
38+
} = useCopy(hookOptions);
39+
40+
const isExternallyControlled = externalIsCopied !== undefined;
41+
const isCopied = isExternallyControlled
42+
? externalIsCopied
43+
: hookIsCopied(uniqueId);
44+
const copiedText = isExternallyControlled
45+
? externalCopiedText || 'Copied ✓'
46+
: hookCopiedText(uniqueId);
47+
48+
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
49+
e.stopPropagation();
50+
51+
if (isExternallyControlled && onCopy) {
52+
onCopy(value);
53+
} else {
54+
copyToClipboard(uniqueId, value);
55+
}
56+
};
57+
58+
return (
59+
<div
60+
className={twMerge(
61+
'inline-flex items-center cursor-pointer hover:opacity-100 transition-opacity duration-200',
62+
className,
63+
)}
64+
onClick={handleClick}
65+
>
66+
<div className='relative'>
67+
{/* Original children */}
68+
<div
69+
className={twMerge(
70+
'transition-opacity duration-300 ease-in-out',
71+
isCopied && 'opacity-0',
72+
)}
73+
>
74+
{children}
75+
</div>
76+
77+
{/* Copied text overlay */}
78+
{isCopied && (
79+
<div
80+
className={twMerge(
81+
'absolute inset-0 flex items-center transition-opacity duration-300 ease-in-out text-green-600',
82+
'opacity-100',
83+
)}
84+
>
85+
{copiedText}
86+
</div>
87+
)}
88+
</div>
89+
</div>
90+
);
91+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Copy } from './Copy';

packages/babylon-core-ui/src/components/Dialog/MobileDialog.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { twJoin } from "tailwind-merge";
44
import { Portal } from "@/components/Portal";
55
import { useModalManager } from "@/hooks/useModalManager";
66
import { Backdrop } from "./components/Backdrop";
7+
import { CloseIcon } from "@/components/Icons";
78

89
export interface MobileDialogProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
910
open?: boolean;
@@ -18,12 +19,23 @@ export const MobileDialog = ({ children, open = false, className, onClose, ...re
1819
<div
1920
{...restProps}
2021
className={twJoin(
21-
"bbn-dialog-mobile",
22+
"bbn-dialog-mobile relative",
2223
open ? "animate-mobile-modal-in" : "animate-mobile-modal-out",
2324
className,
2425
)}
2526
onAnimationEnd={unmount}
2627
>
28+
{/* Close button */}
29+
{onClose && (
30+
<button
31+
onClick={onClose}
32+
className="absolute top-4 left-4 z-10 p-1.5 rounded-full bg-surface-tertiary hover:bg-surface-quaternary transition-colors"
33+
aria-label="Close"
34+
>
35+
<CloseIcon size={14} variant="accent-primary" />
36+
</button>
37+
)}
38+
2739
{children}
2840
</div>
2941

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@
2222
}
2323

2424
&-mobile {
25-
@apply bg-surface border-secondary-strokeLight fixed inset-x-0 bottom-0 z-50 flex max-h-full flex-col rounded-t-3xl border px-4 pb-4 pt-6;
25+
@apply bg-surface border-secondary-strokeLight fixed inset-x-0 bottom-0 z-50 flex max-h-full flex-col rounded-t-3xl border px-4 pb-4 pt-12;
2626
}
2727
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from 'react';
2+
import { Text } from '../Text';
3+
import { trim } from '../../utils/trim';
4+
import { twJoin } from 'tailwind-merge';
5+
6+
interface DisplayHashProps {
7+
value: string;
8+
symbols?: number;
9+
size?: React.ComponentProps<typeof Text>['variant'];
10+
className?: string;
11+
}
12+
13+
export const DisplayHash: React.FC<DisplayHashProps> = ({
14+
value,
15+
symbols = 8,
16+
size = 'body2',
17+
className,
18+
}) => {
19+
if (!value) {
20+
return null;
21+
}
22+
23+
return (
24+
<Text variant={size} className={twJoin('text-accent-primary', className)}>
25+
<span>{trim(value, symbols) ?? value}</span>
26+
</Text>
27+
);
28+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { DisplayHash } from './DisplayHash';
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { twJoin } from "tailwind-merge";
2+
3+
interface ThemedIconProps {
4+
children: React.ReactNode;
5+
className?: string;
6+
size?: number;
7+
variant?: "default" | "primary" | "secondary" | "error" | "success" | "accent-primary" | "accent-secondary";
8+
background?: boolean;
9+
rounded?: boolean;
10+
}
11+
12+
export const ThemedIcon = ({
13+
children,
14+
className = "",
15+
size = 40,
16+
variant = "default",
17+
background = false,
18+
rounded = false,
19+
}: ThemedIconProps) => {
20+
const variants = {
21+
default: "text-accent-secondary",
22+
primary: "text-primary-light",
23+
secondary: "text-accent-secondary",
24+
error: "text-error-main",
25+
success: "text-success-main",
26+
"accent-primary": "text-accent-primary",
27+
"accent-secondary": "text-accent-secondary",
28+
};
29+
30+
const backgroundStyles = {
31+
default: "bg-accent-secondary/10",
32+
primary: "bg-primary-light/10 dark:bg-[#47484A]",
33+
secondary: "bg-accent-secondary/10",
34+
error: "bg-error-main/10",
35+
success: "bg-success-main/10",
36+
"accent-primary": "bg-accent-primary/10",
37+
"accent-secondary": "bg-accent-secondary/10",
38+
};
39+
40+
return (
41+
<div
42+
className={twJoin(
43+
"flex items-center justify-center",
44+
background && backgroundStyles[variant],
45+
rounded && "rounded-full",
46+
background
47+
? "[&_svg]:w-4 [&_svg]:h-4"
48+
: "[&_svg]:w-full [&_svg]:h-full",
49+
"[&_svg_path]:fill-current",
50+
background && "dark:[&_svg_path]:fill-white",
51+
variants[variant],
52+
className,
53+
)}
54+
style={{ width: size, height: size }}
55+
>
56+
{children}
57+
</div>
58+
);
59+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { IconProps, iconColorVariants } from "../index";
2+
import { twJoin } from "tailwind-merge";
3+
4+
export const CloseIcon = ({
5+
className = "",
6+
size = 14,
7+
variant = "default",
8+
color
9+
}: IconProps) => {
10+
const colorClass = color || iconColorVariants[variant];
11+
12+
return (
13+
<svg
14+
style={{ width: size, height: size }}
15+
viewBox="0 0 14 14"
16+
fill="none"
17+
xmlns="http://www.w3.org/2000/svg"
18+
className={twJoin("transition-opacity duration-200", colorClass, className)}
19+
>
20+
<path
21+
d="M14 1.41L12.59 0L7 5.59L1.41 0L0 1.41L5.59 7L0 12.59L1.41 14L7 8.41L12.59 14L14 12.59L8.41 7L14 1.41Z"
22+
fill="currentColor"
23+
/>
24+
</svg>
25+
);
26+
};

0 commit comments

Comments
 (0)