update troubleshooting

This commit is contained in:
Eduard Gert
2026-05-08 17:18:25 +02:00
parent 3953fee5a4
commit 4c3d4effbd
12 changed files with 479 additions and 325 deletions

View File

@@ -1,12 +1,14 @@
import { cva, VariantProps } from "class-variance-authority";
import classNames from "classnames";
import { ButtonHTMLAttributes, forwardRef } from "react";
import { Check, Copy } from "lucide-react";
import { ButtonHTMLAttributes, forwardRef, useState } from "react";
export type ButtonVariants = VariantProps<typeof buttonVariants>;
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, ButtonVariants {
disabled?: boolean;
stopPropagation?: boolean;
copy?: string;
}
export const buttonVariants = cva(
@@ -84,7 +86,7 @@ export const buttonVariants = cva(
],
},
size: {
xs: "text-xs py-2 px-4",
xs: "text-xs py-2 px-3.5",
xs2: "text-[0.78rem] py-2 px-4",
sm: "text-sm py-[9px] px-4",
md: "text-md py-[9px] px-4",
@@ -115,10 +117,13 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
className,
onClick,
disabled,
copy,
...props
},
ref,
) {
const [copied, setCopied] = useState(false);
const iconSize = size === "xs" ? 12 : 14;
return (
<button
ref={ref}
@@ -135,10 +140,21 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
)}
onClick={(e) => {
if (stopPropagation) e.stopPropagation();
if (copy !== undefined) {
void navigator.clipboard
.writeText(copy)
.then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 1500);
})
.catch(() => {});
}
onClick?.(e);
}}
{...props}
>
{copy !== undefined &&
(copied ? <Check size={iconSize} /> : <Copy size={iconSize} />)}
{children}
</button>
);

View File

@@ -1,21 +1,12 @@
import { cva, VariantProps } from "class-variance-authority";
import { ChevronDown, ChevronUp, Eye, EyeOff } from "lucide-react";
import {
forwardRef,
InputHTMLAttributes,
ReactNode,
useId,
useRef,
useState,
} from "react";
import { Check, ChevronDown, ChevronUp, Copy, Eye, EyeOff } from "lucide-react";
import { forwardRef, InputHTMLAttributes, ReactNode, useId, useRef, useState } from "react";
import { cn } from "@/lib/cn";
import { Label } from "@/components/Label";
type InputVariants = VariantProps<typeof inputVariants>;
export interface InputProps
extends InputHTMLAttributes<HTMLInputElement>,
InputVariants {
export interface InputProps extends InputHTMLAttributes<HTMLInputElement>, InputVariants {
label?: string;
customPrefix?: ReactNode;
customSuffix?: ReactNode;
@@ -24,6 +15,7 @@ export interface InputProps
error?: string;
prefixClassName?: string;
showPasswordToggle?: boolean;
copy?: boolean;
}
const inputVariants = cva("", {
@@ -46,9 +38,7 @@ const inputVariants = cva("", {
default: [
"dark:bg-nb-gray-900 border-neutral-200 dark:border-nb-gray-700 text-nb-gray-300",
],
error: [
"dark:bg-nb-gray-900 border-red-500 text-nb-gray-300 text-red-500",
],
error: ["dark:bg-nb-gray-900 border-red-500 text-nb-gray-300 text-red-500"],
},
},
});
@@ -66,19 +56,20 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
variant = "default",
prefixClassName,
showPasswordToggle = false,
copy = false,
id,
...props
},
ref,
) {
const [showPassword, setShowPassword] = useState(false);
const [copied, setCopied] = useState(false);
const isPasswordType = type === "password";
const inputType = isPasswordType && showPassword ? "text" : type;
const isNumber = type === "number";
const reactId = useId();
const inputId =
id ?? (label ? `input-${reactId}` : undefined);
const inputId = id ?? (label ? `input-${reactId}` : undefined);
const internalRef = useRef<HTMLInputElement | null>(null);
const setRefs = (el: HTMLInputElement | null) => {
@@ -118,7 +109,30 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
</button>
) : null;
const suffix = passwordToggle || customSuffix;
const onCopy = async () => {
const text = props.value != null ? String(props.value) : (internalRef.current?.value ?? "");
if (!text) return;
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
} catch {
// ignore
}
};
const copyToggle = copy ? (
<button
type="button"
onClick={onCopy}
className="hover:text-white transition-all pointer-events-auto"
aria-label="Copy"
>
{copied ? <Check size={16} /> : <Copy size={16} />}
</button>
) : null;
const suffix = passwordToggle || copyToggle || customSuffix;
const showStepper = isNumber;
return (
@@ -129,9 +143,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
<div
className={cn(
inputVariants({
prefixSuffixVariant: error
? "error"
: "default",
prefixSuffixVariant: error ? "error" : "default",
}),
"flex h-[40px] w-auto rounded-l-md bg-white px-3 py-2 text-sm",
"border items-center whitespace-nowrap",
@@ -173,7 +185,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
icon && "!pl-10",
"border",
props.readOnly &&
"!bg-nb-gray-920 text-nb-gray-400 !border-nb-gray-800",
"!bg-nb-gray-910 text-nb-gray-400 !border-nb-gray-800",
showStepper &&
"!rounded-r-none [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none [-moz-appearance:textfield]",
className,

View File

@@ -0,0 +1,48 @@
import type { ReactNode } from "react";
import { Check, Loader2, XCircle } from "lucide-react";
import { cn } from "@/lib/cn";
type Variant = "loading" | "success" | "error";
type Props = {
variant: Variant;
title: ReactNode;
description?: ReactNode;
children?: ReactNode;
actions?: ReactNode;
};
const VARIANTS: Record<Variant, { icon: ReactNode; className: string }> = {
loading: {
icon: <Loader2 className={"animate-spin text-nb-gray-950"} size={16} />,
className: "bg-nb-gray-100",
},
success: {
icon: <Check className={"text-white"} size={18} />,
className: "bg-green-500",
},
error: {
icon: <XCircle className={"text-white"} size={18} />,
className: "bg-red-500",
},
};
export function StatusPanel({ variant, title, description, children, actions }: Props) {
const { icon, className } = VARIANTS[variant];
return (
<div className={"absolute inset-0 flex flex-col items-center justify-center gap-5 px-8"}>
<div className={cn("h-9 w-9 rounded-md flex items-center justify-center", className)}>
{icon}
</div>
<div className={"flex flex-col items-center gap-0.5 max-w-md text-center"}>
<p className={"text-base font-medium text-nb-gray-50"}>{title}</p>
{description && <p className={"text-sm text-nb-gray-300"}>{description}</p>}
</div>
{children && <div className={"w-full max-w-md flex flex-col gap-3"}>{children}</div>}
{actions && <div className={"flex items-center gap-2"}>{actions}</div>}
</div>
);
}