[Feature] Add Spinner component
What
RubyUI::Spinner — a port of shadcn/ui Spinner. Inline SVG (lucide loader-circle), role="status", aria-label, data-slot="spinner", size-4 animate-spin. No JS, no Stimulus, no dependencies. Size and colour come from the caller via class:.
Docs page at /docs/spinner with the upstream examples that fit our component set: standalone, sizes, colours, inside Button (disabled + aria-busy), inside Badge, inside Empty as a loading screen, plus Customization and Accessibility sections.
Why
shadcn has no loading prop on Button. It ships a standalone Spinner that gets composed into buttons, badges, input groups and empty states. RubyUI had Skeleton, Progress and Empty but nothing to put inside them — this closes that gap without touching Button, so it can land in any order relative to the Button work.
Departures from upstream (deliberate, each forced by Phlex or by our Button)
label:kwarg instead of overridingaria-labelthrough attrs. Phlexmixconcatenates attribute values, soaria: {label: "Saving"}renderedaria-label="Loading Saving". Same trade-off every other RubyUI component makes (variant:,size:,pressed:).label: nil→ decorative (aria-hidden="true", no role). For a spinner inside a control that announcesaria-busyitself. Upstream nests arole="status"spinner inside the button, which makes the button's accessible name read the label twice. Default stays"Loading"for parity on a standalone spinner.shrink-0on the icon. Our Button lacks shadcn's[&_svg]:shrink-0; this can go once Button gets it.
Everything else passes through **attrs, so data: {icon: "inline-start"} works unchanged when Button learns the vega padding convention.
Also in this PR
Separate first commit: the MCP registry builder ignored standalone Codeblock(<<~RUBY, ...) samples in docs views, leaving empty headings in docs_markdown. Fixed so the Spinner docs (and Form / InputOTP, which had the same gap) render fully for MCP consumers. registry.json, llms.txt, llms-full.txt and sitemap.xml are regenerated.
Test steps
cd gem && bundle exec rake # 294 runs, 0 failures, standardrb clean
cd docs && bin/rails db:test:prepare test && bundle exec standardrb
cd docs && pnpm build:css && grep -c animate-spin app/assets/builds/application.css
cd mcp && bundle exec rake test # 30 runs, 0 failures
Then cd docs && bin/dev, open /docs/spinner, toggle dark mode. Inspect a Button example: the <button> carries aria-busy="true", the <svg> inside carries aria-hidden="true".
Screenshots (light / dark) to follow.
Summary by cubic
Adds RubyUI::Spinner, an inline SVG loading indicator ported from shadcn/ui, with no JS, Stimulus, or icon dependencies. Size and color come from the caller via class:, so it composes into Button, Badge, and Empty without changes to those components.
New Features
- Standalone spinners announce themselves with
role="status"and anaria-labeldefaulting to "Loading"; passlabel:to describe the operation. - Pass
label: nil(or a blank label) to render the spinner decorative (aria-hidden="true") inside a control that announcesaria-busyitself. - Adds a docs page at
/docs/spinnercovering sizes, colors, composition, customization, and accessibility.
Bug Fixes
- Fixes the MCP registry builder so standalone
Codeblock(<<~RUBY, ...)samples in docs views are included indocs_markdown; Form and InputOTP previously rendered with empty headings. - Blank or whitespace
label:values now render as decorative instead of an emptyrole="status"live region. - Regenerates
registry.json,llms.txt,llms-full.txt, andsitemap.xml.