[client/ui] Replace fyne UI with Wails (rename ui-wails to ui)

Removes the legacy fyne-based client/ui implementation and renames the
Wails replacement (client/ui-wails) to take its place at client/ui. Go
imports, frontend bindings, CI workflows, goreleaser configs and the
windows .syso icon path are updated to follow the rename.
This commit is contained in:
Zoltán Papp
2026-05-11 11:20:22 +02:00
parent 08f52f4517
commit 9aef31ff53
189 changed files with 82 additions and 5840 deletions

View File

@@ -0,0 +1,42 @@
import { ButtonHTMLAttributes, forwardRef } from "react";
import { cn } from "../lib/cn";
type Variant = "primary" | "secondary" | "ghost" | "danger";
type Size = "sm" | "md";
const variants: Record<Variant, string> = {
primary: "bg-netbird text-white hover:bg-netbird-500 disabled:bg-nb-gray-300",
secondary:
"bg-nb-gray-100 text-nb-gray-900 hover:bg-nb-gray-200 dark:bg-nb-gray-900 dark:text-nb-gray-50 dark:hover:bg-nb-gray-800",
ghost:
"bg-transparent text-nb-gray-700 hover:bg-nb-gray-100 dark:text-nb-gray-200 dark:hover:bg-nb-gray-900",
danger: "bg-red-600 text-white hover:bg-red-500",
};
const sizes: Record<Size, string> = {
sm: "h-7 px-2 text-xs",
md: "h-9 px-3 text-sm",
};
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: Variant;
size?: Size;
}
export const Button = forwardRef<HTMLButtonElement, Props>(function Button(
{ variant = "primary", size = "md", className, ...rest },
ref,
) {
return (
<button
ref={ref}
className={cn(
"inline-flex items-center justify-center gap-2 rounded-md font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-60",
variants[variant],
sizes[size],
className,
)}
{...rest}
/>
);
});

View File

@@ -0,0 +1,14 @@
import { HTMLAttributes } from "react";
import { cn } from "../lib/cn";
export function Card({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn(
"rounded-lg border border-nb-gray-200 bg-white p-4 dark:border-nb-gray-800 dark:bg-nb-gray-925",
className,
)}
{...rest}
/>
);
}

View File

@@ -0,0 +1,33 @@
import { InputHTMLAttributes, forwardRef } from "react";
import { cn } from "../lib/cn";
interface Props extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
}
export const Input = forwardRef<HTMLInputElement, Props>(function Input(
{ label, className, id, ...rest },
ref,
) {
const inputId = id ?? label?.toLowerCase().replace(/\s+/g, "-");
return (
<div className="flex flex-col gap-1">
{label && (
<label htmlFor={inputId} className="text-xs font-medium text-nb-gray-600 dark:text-nb-gray-300">
{label}
</label>
)}
<input
id={inputId}
ref={ref}
className={cn(
"h-9 rounded-md border border-nb-gray-300 bg-white px-3 text-sm",
"focus:border-netbird focus:outline-none focus:ring-1 focus:ring-netbird",
"dark:border-nb-gray-700 dark:bg-nb-gray-925 dark:text-nb-gray-50",
className,
)}
{...rest}
/>
</div>
);
});

View File

@@ -0,0 +1,42 @@
import { cn } from "../lib/cn";
interface Props {
checked: boolean;
onChange: (checked: boolean) => void;
disabled?: boolean;
label?: string;
description?: string;
}
export function Switch({ checked, onChange, disabled, label, description }: Props) {
return (
<label className={cn("flex items-start gap-3", disabled && "opacity-60")}>
<button
type="button"
role="switch"
aria-checked={checked}
disabled={disabled}
onClick={() => onChange(!checked)}
className={cn(
"mt-0.5 inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors",
checked ? "bg-netbird" : "bg-nb-gray-300 dark:bg-nb-gray-700",
)}
>
<span
className={cn(
"inline-block h-4 w-4 transform rounded-full bg-white transition-transform",
checked ? "translate-x-4" : "translate-x-0.5",
)}
/>
</button>
{(label || description) && (
<span className="flex flex-col">
{label && <span className="text-sm font-medium">{label}</span>}
{description && (
<span className="text-xs text-nb-gray-500">{description}</span>
)}
</span>
)}
</label>
);
}

View File

@@ -0,0 +1,40 @@
import { ReactNode, useState } from "react";
import { cn } from "../lib/cn";
interface Tab {
value: string;
label: string;
content: ReactNode;
}
interface Props {
tabs: Tab[];
initial?: string;
}
export function Tabs({ tabs, initial }: Props) {
const [active, setActive] = useState(initial ?? tabs[0]?.value);
return (
<div className="flex h-full flex-col">
<div className="flex shrink-0 gap-1 border-b border-nb-gray-200 dark:border-nb-gray-800">
{tabs.map((t) => (
<button
key={t.value}
onClick={() => setActive(t.value)}
className={cn(
"border-b-2 px-3 py-2 text-sm font-medium transition-colors",
active === t.value
? "border-netbird text-netbird"
: "border-transparent text-nb-gray-500 hover:text-nb-gray-800 dark:hover:text-nb-gray-200",
)}
>
{t.label}
</button>
))}
</div>
<div className="flex-1 overflow-auto">
{tabs.find((t) => t.value === active)?.content}
</div>
</div>
);
}