feat(desktop): add redis gui foundation baseline
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2326
apps/desktop/src/App.tsx
Normal file
2326
apps/desktop/src/App.tsx
Normal file
File diff suppressed because it is too large
Load Diff
32
apps/desktop/src/components/ui/badge.tsx
Normal file
32
apps/desktop/src/components/ui/badge.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full border px-2.5 py-1 font-mono text-[11px] uppercase tracking-[0.18em]",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
neutral:
|
||||
"border-[var(--line-soft)] bg-[var(--surface-base)] text-[var(--ink-muted)]",
|
||||
accent:
|
||||
"border-[var(--accent-strong)] bg-[var(--accent-faint)] text-[var(--accent-strong)]",
|
||||
danger:
|
||||
"border-[var(--danger-soft)] bg-[var(--danger-faint)] text-[var(--danger-strong)]",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "neutral",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
||||
}
|
||||
|
||||
export { Badge };
|
||||
51
apps/desktop/src/components/ui/button.tsx
Normal file
51
apps/desktop/src/components/ui/button.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 rounded-full border text-sm font-medium transition outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-strong)] focus-visible:ring-offset-2 focus-visible:ring-offset-transparent disabled:pointer-events-none disabled:opacity-45",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-[var(--accent-strong)] bg-[var(--accent-strong)] text-white hover:opacity-92",
|
||||
outline:
|
||||
"border-[var(--line-soft)] bg-[var(--surface-base)] text-[var(--ink-0)] hover:border-[var(--line-strong)]",
|
||||
subtle:
|
||||
"border-[var(--line-soft)] bg-[var(--surface-muted)] text-[var(--ink-0)] hover:border-[var(--line-strong)]",
|
||||
danger:
|
||||
"border-[var(--danger-soft)] bg-[var(--danger-strong)] text-white hover:opacity-92",
|
||||
ghost:
|
||||
"border-transparent bg-transparent text-[var(--ink-soft)] hover:border-[var(--line-soft)] hover:bg-[var(--surface-base)] hover:text-[var(--ink-0)]",
|
||||
},
|
||||
size: {
|
||||
default: "px-4 py-2.5",
|
||||
sm: "px-3 py-2",
|
||||
lg: "px-5 py-3",
|
||||
icon: "h-10 w-10 rounded-2xl p-0",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, type = "button", ...props }, ref) => (
|
||||
<button
|
||||
ref={ref}
|
||||
type={type}
|
||||
className={cn(buttonVariants({ variant, size }), className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants };
|
||||
102
apps/desktop/src/components/ui/dialog.tsx
Normal file
102
apps/desktop/src/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import * as React from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { X } from "lucide-react";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
const DialogPortal = DialogPrimitive.Portal;
|
||||
const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-[color:color-mix(in_srgb,var(--ink-0)_24%,transparent)] backdrop-blur-[2px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-1/2 top-1/2 z-50 grid w-[min(100%-2rem,44rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-[28px] border border-[var(--line-soft)] bg-[var(--surface-raised)] p-6 shadow-[var(--shadow-panel)] outline-none",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-2xl border border-[var(--line-soft)] bg-[var(--surface-base)] p-2 text-[var(--ink-muted)] transition hover:border-[var(--line-strong)] hover:text-[var(--ink-0)]">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
function DialogHeader({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn("flex flex-col gap-2", className)} {...props} />;
|
||||
}
|
||||
|
||||
function DialogFooter({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("flex flex-wrap justify-end gap-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-2xl font-semibold tracking-[-0.03em] text-[var(--ink-0)]", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm leading-6 text-[var(--ink-soft)]", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
};
|
||||
18
apps/desktop/src/components/ui/input.tsx
Normal file
18
apps/desktop/src/components/ui/input.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as React from "react";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
||||
({ className, ...props }, ref) => (
|
||||
<input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-11 w-full rounded-2xl border border-[var(--line-soft)] bg-[var(--surface-base)] px-3 py-2 text-sm text-[var(--ink-0)] outline-none transition placeholder:text-[var(--ink-muted)] focus-visible:border-[var(--accent-strong)] focus-visible:ring-2 focus-visible:ring-[color:color-mix(in_srgb,var(--accent-strong)_18%,transparent)] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input };
|
||||
19
apps/desktop/src/components/ui/textarea.tsx
Normal file
19
apps/desktop/src/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from "react";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
const Textarea = React.forwardRef<
|
||||
HTMLTextAreaElement,
|
||||
React.ComponentProps<"textarea">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<textarea
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex min-h-28 w-full rounded-[22px] border border-[var(--line-soft)] bg-[var(--surface-base)] px-4 py-3 text-sm text-[var(--ink-0)] outline-none transition placeholder:text-[var(--ink-muted)] focus-visible:border-[var(--accent-strong)] focus-visible:ring-2 focus-visible:ring-[color:color-mix(in_srgb,var(--accent-strong)_18%,transparent)] disabled:cursor-not-allowed disabled:opacity-60",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Textarea.displayName = "Textarea";
|
||||
|
||||
export { Textarea };
|
||||
79
apps/desktop/src/index.css
Normal file
79
apps/desktop/src/index.css
Normal file
@@ -0,0 +1,79 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
color-scheme: light;
|
||||
font-family: "IBM Plex Sans", "Aptos", "Segoe UI", sans-serif;
|
||||
--surface-base: #f3f0ea;
|
||||
--surface-muted: #ebe6de;
|
||||
--surface-raised: #fbfaf7;
|
||||
--surface-strong: #f1ede7;
|
||||
--line-soft: #d9d0c4;
|
||||
--line-strong: #b5a897;
|
||||
--ink-0: #1f1d1a;
|
||||
--ink-soft: #4f4a44;
|
||||
--ink-muted: #7f766d;
|
||||
--accent-strong: #1f5c52;
|
||||
--accent-faint: #dcece7;
|
||||
--danger-strong: #9b3b2e;
|
||||
--danger-soft: #d7a39a;
|
||||
--danger-faint: #f6e5e1;
|
||||
--shadow-panel: 0 18px 48px rgba(34, 29, 25, 0.09);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(247, 243, 236, 0.96) 0%, rgba(238, 232, 222, 0.92) 48%, rgba(232, 226, 214, 0.9) 100%);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--surface-base: #181715;
|
||||
--surface-muted: #201e1a;
|
||||
--surface-raised: #24221e;
|
||||
--surface-strong: #2d2a25;
|
||||
--line-soft: #433f38;
|
||||
--line-strong: #5f584d;
|
||||
--ink-0: #f1ece4;
|
||||
--ink-soft: #d2c8bc;
|
||||
--ink-muted: #9e9487;
|
||||
--accent-strong: #7ec2b5;
|
||||
--accent-faint: rgba(71, 124, 114, 0.22);
|
||||
--danger-strong: #f08c7d;
|
||||
--danger-soft: rgba(210, 110, 92, 0.42);
|
||||
--danger-faint: rgba(151, 63, 49, 0.18);
|
||||
--shadow-panel: 0 20px 56px rgba(0, 0, 0, 0.32);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(35, 32, 29, 0.98) 0%, rgba(21, 20, 18, 0.96) 52%, rgba(16, 15, 13, 0.98) 100%);
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
background: transparent;
|
||||
color: var(--ink-0);
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: color-mix(in srgb, var(--accent-strong) 24%, transparent);
|
||||
}
|
||||
6
apps/desktop/src/lib/utils.ts
Normal file
6
apps/desktop/src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
10
apps/desktop/src/main.tsx
Normal file
10
apps/desktop/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
Reference in New Issue
Block a user