mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
ui: redesign tabs
This commit is contained in:
@@ -116,7 +116,7 @@ body {
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--destructive: #ff543a;
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
|
||||
@@ -14,14 +14,20 @@ export function AppBreadcrumb() {
|
||||
const breadcrumbs = useBreadcrumbs();
|
||||
|
||||
return (
|
||||
<Breadcrumb className={cn("mb-2", { invisible: breadcrumbs.length <= 1 })}>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbLink asChild></BreadcrumbLink>
|
||||
<BreadcrumbList>
|
||||
{breadcrumbs.length === 1 && (
|
||||
<BreadcrumbItem>
|
||||
<Link to="/">Ironmount</Link>
|
||||
</BreadcrumbItem>
|
||||
)}
|
||||
{breadcrumbs.map((breadcrumb, index) => {
|
||||
const isLast = index === breadcrumbs.length - 1;
|
||||
|
||||
return (
|
||||
<div key={`${breadcrumb.label}-${index}`} className="contents">
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbItem className={cn({ invisible: breadcrumbs.length <= 1 })}>
|
||||
{isLast || breadcrumb.isCurrentPage ? (
|
||||
<BreadcrumbPage>{breadcrumb.label}</BreadcrumbPage>
|
||||
) : breadcrumb.href ? (
|
||||
|
||||
@@ -33,7 +33,7 @@ export const CreateVolumeDialog = ({ open, setOpen }: Props) => {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="bg-transparent border hover:bg-strong-accent">
|
||||
<Button>
|
||||
<Plus size={16} className="mr-2" />
|
||||
Create volume
|
||||
</Button>
|
||||
|
||||
@@ -10,12 +10,12 @@ const buttonVariants = cva(
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-[var(--strong-accent)] text-white hover:bg-[var(--strong-accent)]/90 focus-visible:ring-[var(--strong-accent)]/50",
|
||||
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/50",
|
||||
default: "bg-transparent text-white hover:bg-[#3A3A3A]/80 border dark:text-white dark:hover:bg-[#3A3A3A]/80",
|
||||
primary: "bg-strong-accent text-white hover:bg-strong-accent/90 focus-visible:ring-strong-accent/50",
|
||||
destructive:
|
||||
"border border-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/50 text-destructive hover:text-white",
|
||||
outline: "border border-border bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-[#464646] text-white hover:bg-[#464646]/80 dark:bg-[#464646] dark:text-white dark:hover:bg-[#464646]/80",
|
||||
secondary: "bg-transparent text-white hover:bg-[#3A3A3A]/80 border dark:text-white dark:hover:bg-[#3A3A3A]/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect } from "react";
|
||||
import Prism from "prismjs";
|
||||
import "prismjs/themes/prism-twilight.css";
|
||||
import type React from "react";
|
||||
import { useEffect } from "react";
|
||||
import "prismjs/themes/prism-okaidia.css";
|
||||
import "prismjs/components/prism-yaml";
|
||||
import { toast } from "sonner";
|
||||
import { copyToClipboard } from "~/utils/clipboard";
|
||||
@@ -22,8 +23,8 @@ export const CodeBlock: React.FC<CodeBlockProps> = ({ code, language = "jsx", fi
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-sm bg-slate-900 ring-1 ring-white/10">
|
||||
<div className="flex items-center justify-between border-b border-white/10 px-4 py-2 text-xs text-slate-400">
|
||||
<div className="overflow-hidden rounded-sm bg-card-header ring-1 ring-white/10">
|
||||
<div className="flex items-center justify-between border-b border-white/10 px-4 py-2 text-xs">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-rose-500" />
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-amber-500" />
|
||||
@@ -33,12 +34,12 @@ export const CodeBlock: React.FC<CodeBlockProps> = ({ code, language = "jsx", fi
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleCopy()}
|
||||
className="cursor-pointer rounded-md bg-white/5 px-2 py-1 text-[11px] font-medium text-slate-300 ring-1 ring-inset ring-white/10 transition hover:bg-white/10 active:translate-y-px"
|
||||
className="cursor-pointer rounded-md bg-white/5 px-2 py-1 text-[11px] font-medium ring-1 ring-inset ring-white/10 transition hover:bg-white/10 active:translate-y-px"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre className="overflow-x-auto leading-6 text-xs m-0" style={{ marginTop: 0, marginBottom: 0 }}>
|
||||
<pre className="overflow-x-auto text-xs m-0" style={{ marginTop: 0, borderRadius: 0, marginBottom: 0 }}>
|
||||
<code className={`language-${language}`}>{code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
@@ -1,64 +1,58 @@
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||
import type * as React from "react";
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
function Tabs({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
||||
return (
|
||||
<TabsPrimitive.Root
|
||||
data-slot="tabs"
|
||||
className={cn("flex flex-col gap-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
function Tabs({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
||||
return <TabsPrimitive.Root data-slot="tabs" className={cn("flex flex-col gap-2", className)} {...props} />;
|
||||
}
|
||||
|
||||
function TabsList({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
||||
return (
|
||||
<TabsPrimitive.List
|
||||
data-slot="tabs-list"
|
||||
className={cn(
|
||||
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
function TabsList({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.List>) {
|
||||
return (
|
||||
<TabsPrimitive.List
|
||||
data-slot="tabs-list"
|
||||
className={cn("inline-flex h-7 items-center gap-4 text-xs text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function TabsTrigger({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
||||
return (
|
||||
<TabsPrimitive.Trigger
|
||||
data-slot="tabs-trigger"
|
||||
className={cn(
|
||||
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
||||
return (
|
||||
<TabsPrimitive.Trigger
|
||||
data-slot="tabs-trigger"
|
||||
className={cn(
|
||||
"cursor-pointer group relative inline-flex h-7 items-center whitespace-nowrap text-xs font-medium transition-colors",
|
||||
"text-muted-foreground data-[state=active]:text-foreground disabled:pointer-events-none disabled:opacity-50",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
||||
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||
// Padding: 20px horizontal (8px for bracket tick + 12px gap to text)
|
||||
"px-5",
|
||||
// Transparent orange background for active state
|
||||
"data-[state=active]:bg-[#FF453A]/10",
|
||||
// Left bracket - vertical line
|
||||
"before:absolute before:left-0 before:top-0 before:h-7 before:w-0.5 before:bg-[#5D6570] before:transition-colors data-[state=active]:before:bg-[#FF453A]",
|
||||
// Left bracket - top tick
|
||||
"after:absolute after:left-0 after:top-[-1px] after:w-2 after:h-0.5 after:bg-[#5D6570] after:transition-colors data-[state=active]:after:bg-[#FF453A]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="relative z-10">{props.children}</span>
|
||||
{/* Left bracket - bottom tick */}
|
||||
<span className="absolute left-0 bottom-[-1px] h-0.5 w-2 bg-[#5D6570] transition-colors group-data-[state=active]:bg-[#FF453A]" />
|
||||
{/* Right bracket - top tick */}
|
||||
<span className="absolute right-0 top-[-1px] h-0.5 w-2 bg-[#5D6570] transition-colors group-data-[state=active]:bg-[#FF453A]" />
|
||||
{/* Right bracket - vertical line */}
|
||||
<span className="absolute right-0 top-0 h-7 w-0.5 bg-[#5D6570] transition-colors group-data-[state=active]:bg-[#FF453A]" />
|
||||
{/* Right bracket - bottom tick */}
|
||||
<span className="absolute right-0 bottom-[-1px] h-0.5 w-2 bg-[#5D6570] transition-colors group-data-[state=active]:bg-[#FF453A]" />
|
||||
</TabsPrimitive.Trigger>
|
||||
);
|
||||
}
|
||||
|
||||
function TabsContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
||||
return (
|
||||
<TabsPrimitive.Content
|
||||
data-slot="tabs-content"
|
||||
className={cn("flex-1 outline-none", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
||||
return <TabsPrimitive.Content data-slot="tabs-content" className={cn("flex-1 outline-none", className)} {...props} />;
|
||||
}
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||
|
||||
@@ -111,7 +111,6 @@ export default function DetailsPage({ loaderData }: Route.ComponentProps) {
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => mountVol.mutate({ path: { name } })}
|
||||
loading={mountVol.isPending}
|
||||
className={cn({ hidden: volume.status === "mounted" })}
|
||||
@@ -131,8 +130,8 @@ export default function DetailsPage({ loaderData }: Route.ComponentProps) {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs defaultValue="info" className="mt-0">
|
||||
<TabsList>
|
||||
<Tabs defaultValue="info" className="mt-4">
|
||||
<TabsList className="mb-2">
|
||||
<TabsTrigger value="info">Configuration</TabsTrigger>
|
||||
<TabsTrigger value="docker">Docker</TabsTrigger>
|
||||
<TabsTrigger value="backups">Backups</TabsTrigger>
|
||||
|
||||
@@ -7,12 +7,12 @@ import { listVolumesOptions } from "~/api-client/@tanstack/react-query.gen";
|
||||
import { CreateVolumeDialog } from "~/components/create-volume-dialog";
|
||||
import { StatusDot } from "~/components/status-dot";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Card } from "~/components/ui/card";
|
||||
import { Input } from "~/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
|
||||
import { VolumeIcon } from "~/components/volume-icon";
|
||||
import type { Route } from "./+types/home";
|
||||
import { Card, CardHeader } from "~/components/ui/card";
|
||||
|
||||
export function meta(_: Route.MetaArgs) {
|
||||
return [
|
||||
|
||||
Reference in New Issue
Block a user