mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-21 08:09:55 +00:00
update troubleshooting
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
48
client/ui-wails/frontend/src/components/StatusPanel.tsx
Normal file
48
client/ui-wails/frontend/src/components/StatusPanel.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user