Spinner
Styled loading spinner (Loader2 icon in a status wrapper).
Installation
Add this component to your project:
pnpm dlx shadcn@latest add @foldcn/spinnerSource
The component ships as plain source — no build step, no wrapper. Copy it and make it yours.
registry/default/ui/spinner.ts44 linesimport type { Attribute, ChildAttribute, Html, HtmlBuilder } from 'foldkit/html'
import type { IconNode } from 'lucide'
import { Loader2 } from 'lucide'
import { cn } from '@/lib/utils'
export const spinnerClass = 'size-4 animate-spin'
type StyleConfig = Readonly<{ className?: string }>
const nodeToAttributes = <M>(
attrs: Record<string, string>,
h: HtmlBuilder<M>,
): ReadonlyArray<Attribute<M> | ChildAttribute> =>
Object.entries(attrs).map(([name, value]) => h.Attribute(name, value))
const renderIconNode = <M>(node: IconNode, h: HtmlBuilder<M>): ReadonlyArray<Html> =>
node.map(([tag, attrs]) => {
const stringAttrs: Record<string, string> = Object.fromEntries(
Object.entries(attrs).map(([name, value]) => [name, String(value)]),
)
const attributes = nodeToAttributes(stringAttrs, h)
return tag === 'path' ? h.path(attributes) : h.circle(attributes)
})
/** Styled loading spinner — a `Loader2` lucide svg with `role="status"`. */
export const spinner = <M>(config: StyleConfig, h: HtmlBuilder<M>): Html =>
h.svg(
[
h.DataAttribute('slot', 'spinner'),
h.Role('status'),
h.AriaLabel('Loading'),
h.Class(cn(spinnerClass, config.className)),
h.Xmlns('http://www.w3.org/2000/svg'),
h.Fill('none'),
h.ViewBox('0 0 24 24'),
h.StrokeWidth('2'),
h.Stroke('currentColor'),
h.StrokeLinecap('round'),
h.StrokeLinejoin('round'),
],
renderIconNode(Loader2, h),
)