Use the brand loading mark instead of lucide's spinner
Copies index/frontend's FluksioLoader component and its `.fluksio-loader-line` rule into the app (the shadcn duplication model, so the CSS travels with the component) and swaps every Loader2 pending state over: the router's pending screen, the flow dock's Run and Publish, the dashboard editor's Publish, the overview toolbar's Publish-all and LoadingButton. One loading mark across both shells. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1moruzue2kTJd3uVisgNk
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Loader2 } from "lucide-react"
|
import { FluksioLoader } from "@/components/ui/fluksio-loader"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The router's pending screen, shown while a page's code chunk is on its way.
|
* The router's pending screen, shown while a page's code chunk is on its way.
|
||||||
@@ -15,7 +15,7 @@ export function PageLoading() {
|
|||||||
data-testid="page-loading"
|
data-testid="page-loading"
|
||||||
className="flex min-h-64 items-center justify-center"
|
className="flex min-h-64 items-center justify-center"
|
||||||
>
|
>
|
||||||
<Loader2 className="size-6 animate-spin text-muted-foreground" />
|
<FluksioLoader className="size-6 text-muted-foreground" />
|
||||||
</output>
|
</output>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useMutation } from "@tanstack/react-query"
|
import { useMutation } from "@tanstack/react-query"
|
||||||
import { Check, Loader2, Plus, Search, Trash2 } from "lucide-react"
|
import { Check, Plus, Search, Trash2 } from "lucide-react"
|
||||||
import { motion } from "motion/react"
|
import { motion } from "motion/react"
|
||||||
import { type ReactNode, useRef, useState } from "react"
|
import { type ReactNode, useRef, useState } from "react"
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
|
import { FluksioLoader } from "@/components/ui/fluksio-loader"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -170,7 +171,7 @@ export function OverviewToolbar({
|
|||||||
aria-label="Publish all changes"
|
aria-label="Publish all changes"
|
||||||
data-testid="publish-all"
|
data-testid="publish-all"
|
||||||
>
|
>
|
||||||
{publishing ? <Loader2 className="animate-spin" /> : <Check />}
|
{publishing ? <FluksioLoader /> : <Check />}
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
import { useNavigate } from "@tanstack/react-router"
|
import { useNavigate } from "@tanstack/react-router"
|
||||||
import {
|
import { Check, ExternalLink, Pencil, Plus, Settings2, X } from "lucide-react"
|
||||||
Check,
|
|
||||||
ExternalLink,
|
|
||||||
Loader2,
|
|
||||||
Pencil,
|
|
||||||
Plus,
|
|
||||||
Settings2,
|
|
||||||
X,
|
|
||||||
} from "lucide-react"
|
|
||||||
import { motion } from "motion/react"
|
import { motion } from "motion/react"
|
||||||
import { type CSSProperties, useEffect, useRef, useState } from "react"
|
import { type CSSProperties, useEffect, useRef, useState } from "react"
|
||||||
import {
|
import {
|
||||||
@@ -36,6 +28,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
|
import { FluksioLoader } from "@/components/ui/fluksio-loader"
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
@@ -720,7 +713,7 @@ export function DashboardEditor({
|
|||||||
data-testid="publish-dashboard"
|
data-testid="publish-dashboard"
|
||||||
>
|
>
|
||||||
{save.isPending || publish.isPending ? (
|
{save.isPending || publish.isPending ? (
|
||||||
<Loader2 className="size-3.5 animate-spin" />
|
<FluksioLoader className="size-3.5" />
|
||||||
) : (
|
) : (
|
||||||
<Check className="size-3.5" />
|
<Check className="size-3.5" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { useReactFlow } from "@xyflow/react"
|
|||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
Check,
|
Check,
|
||||||
Loader2,
|
|
||||||
Maximize2,
|
Maximize2,
|
||||||
Pause,
|
Pause,
|
||||||
Pencil,
|
Pencil,
|
||||||
@@ -18,6 +17,7 @@ import { useEffect, useState } from "react"
|
|||||||
|
|
||||||
import type { ValidationIssue } from "@/client"
|
import type { ValidationIssue } from "@/client"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { FluksioLoader } from "@/components/ui/fluksio-loader"
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -273,7 +273,7 @@ export function FlowDock({
|
|||||||
data-testid="run-flow"
|
data-testid="run-flow"
|
||||||
>
|
>
|
||||||
{running ? (
|
{running ? (
|
||||||
<Loader2 className="size-4 animate-spin" />
|
<FluksioLoader className="size-4" />
|
||||||
) : (
|
) : (
|
||||||
<Play className="size-4" />
|
<Play className="size-4" />
|
||||||
)}
|
)}
|
||||||
@@ -348,7 +348,7 @@ export function FlowDock({
|
|||||||
{!connected ? (
|
{!connected ? (
|
||||||
<WifiOff className="size-3.5" />
|
<WifiOff className="size-3.5" />
|
||||||
) : saving || publishing ? (
|
) : saving || publishing ? (
|
||||||
<Loader2 className="size-3.5 animate-spin" />
|
<FluksioLoader className="size-3.5" />
|
||||||
) : (
|
) : (
|
||||||
<Check className="size-3.5" />
|
<Check className="size-3.5" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* FluksioLoader: the app's shared loading mark.
|
||||||
|
*
|
||||||
|
* The brand mark — four neurons in a ring — drawn as one continuous stroke
|
||||||
|
* that traces itself and wipes away in a loop (see the `.fluksio-loader-line`
|
||||||
|
* rule + `fluksio-draw` keyframes in index.css, which also guard
|
||||||
|
* `prefers-reduced-motion`: `MotionConfig` covers JS animations, not CSS ones).
|
||||||
|
* Renders a bare <svg> so it drops in for a lucide icon: size it with a
|
||||||
|
* Tailwind `size-*` class and color it via a `text-*` class (everything is
|
||||||
|
* currentColor).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ring, clockwise from the top node: a 45° connector, then 270° around the
|
||||||
|
* node it lands on, four times over. Geometry from `assets/fluksio-light.svg`
|
||||||
|
* — same node centres and radius, with the connectors pulled onto the circles
|
||||||
|
* so the whole mark is a single subpath and the dash traces it in one lap.
|
||||||
|
*
|
||||||
|
* Stroke weight is the mark's own 1.5 rather than the weight 2 of the glyphs in
|
||||||
|
* `components/icons/`: at 2 the node rings close up into dots.
|
||||||
|
*/
|
||||||
|
const RING =
|
||||||
|
"M 13.71 5.84 L 18.16 10.29 A 2.424 2.424 0 1 1 18.16 13.71 L 13.71 18.16 A 2.424 2.424 0 1 1 10.29 18.16 L 5.84 13.71 A 2.424 2.424 0 1 1 5.84 10.29 L 10.29 5.84 A 2.424 2.424 0 1 1 13.71 5.84 Z"
|
||||||
|
|
||||||
|
export function FluksioLoader({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
strokeLinecap="round"
|
||||||
|
className={className}
|
||||||
|
role="status"
|
||||||
|
aria-label="Loading"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path className="fluksio-loader-line" pathLength={1} d={RING} />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Slot, Slottable } from "@radix-ui/react-slot"
|
import { Slot, Slottable } from "@radix-ui/react-slot"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { Loader2 } from "lucide-react"
|
import { FluksioLoader } from "@/components/ui/fluksio-loader"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
@@ -58,7 +58,7 @@ function LoadingButton({
|
|||||||
disabled={loading || disabled}
|
disabled={loading || disabled}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{loading && <Loader2 className="mr-2 h-5 w-5 animate-spin" />}
|
{loading && <FluksioLoader className="mr-2 h-5 w-5" />}
|
||||||
<Slottable>{children}</Slottable>
|
<Slottable>{children}</Slottable>
|
||||||
</Comp>
|
</Comp>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -211,3 +211,36 @@
|
|||||||
font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
|
font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Loading mark (FluksioLoader) ───────────────────────────────────── */
|
||||||
|
/*
|
||||||
|
* The brand ring is a single subpath with `pathLength="1"`, so a `1 1` dash is
|
||||||
|
* exactly one lap of it: offset 1 hides the mark, 0 completes it, and -1 wipes
|
||||||
|
* it off the same end it was drawn from. The hold in the middle is what makes
|
||||||
|
* the mark legible once per lap instead of only flashing through.
|
||||||
|
*/
|
||||||
|
@keyframes fluksio-draw {
|
||||||
|
0% {
|
||||||
|
stroke-dashoffset: 1;
|
||||||
|
}
|
||||||
|
45%,
|
||||||
|
55% {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fluksio-loader-line {
|
||||||
|
stroke-dasharray: 1 1;
|
||||||
|
animation: fluksio-draw 1.6s var(--ease-standard) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* `MotionConfig reducedMotion="user"` reaches JS animations only. */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.fluksio-loader-line {
|
||||||
|
stroke-dasharray: none;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user