feat(ui): add Spinner component from multica
Port the SpinKit Grid 3x3 spinner as a shared UI component. Uses currentColor and em sizing for flexible theming and scaling. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
01790a57d2
commit
f348d91c18
2 changed files with 77 additions and 0 deletions
36
packages/ui/src/components/spinner.tsx
Normal file
36
packages/ui/src/components/spinner.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Spinner - 3x3 grid spinner based on SpinKit Grid
|
||||
*
|
||||
* Features:
|
||||
* - Uses currentColor (inherits text color from parent, typically theme primary)
|
||||
* - Uses em sizing (scales with font-size)
|
||||
* - 3x3 grid of cubes with staggered scale animation
|
||||
* - Pure CSS animation (no JS state)
|
||||
*
|
||||
* Usage:
|
||||
* <Spinner className="text-primary" /> // Uses primary theme color
|
||||
* <Spinner className="text-muted-foreground" /> // Uses muted color
|
||||
* <Spinner className="text-xs" /> // Controls size via Tailwind font-size
|
||||
*/
|
||||
import { cn } from "@multica/ui/lib/utils"
|
||||
|
||||
export interface SpinnerProps {
|
||||
/** Additional className for styling (color via text-*, size via Tailwind text-*) */
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Spinner({ className }: SpinnerProps) {
|
||||
return (
|
||||
<span className={cn("spinner", className)} role="status" aria-label="Loading">
|
||||
<span className="spinner-cube" />
|
||||
<span className="spinner-cube" />
|
||||
<span className="spinner-cube" />
|
||||
<span className="spinner-cube" />
|
||||
<span className="spinner-cube" />
|
||||
<span className="spinner-cube" />
|
||||
<span className="spinner-cube" />
|
||||
<span className="spinner-cube" />
|
||||
<span className="spinner-cube" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
|
@ -141,3 +141,44 @@
|
|||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* SPINNER - SpinKit Grid (3x3) */
|
||||
.spinner {
|
||||
display: inline-grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
gap: 0.08em;
|
||||
}
|
||||
|
||||
.spinner-cube {
|
||||
background-color: currentColor;
|
||||
animation: spinner-grid 1.3s infinite ease-in-out;
|
||||
transform: scale3d(0.5, 0.5, 1);
|
||||
}
|
||||
|
||||
.spinner-cube:nth-child(1) { animation-delay: 0.2s; }
|
||||
.spinner-cube:nth-child(2) { animation-delay: 0.3s; }
|
||||
.spinner-cube:nth-child(3) { animation-delay: 0.4s; }
|
||||
.spinner-cube:nth-child(4) { animation-delay: 0.1s; }
|
||||
.spinner-cube:nth-child(5) { animation-delay: 0.2s; }
|
||||
.spinner-cube:nth-child(6) { animation-delay: 0.3s; }
|
||||
.spinner-cube:nth-child(7) { animation-delay: 0s; }
|
||||
.spinner-cube:nth-child(8) { animation-delay: 0.1s; }
|
||||
.spinner-cube:nth-child(9) { animation-delay: 0.2s; }
|
||||
|
||||
@keyframes spinner-grid {
|
||||
0%, 70%, 100% {
|
||||
transform: scale3d(0.5, 0.5, 1);
|
||||
}
|
||||
35% {
|
||||
transform: scale3d(0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.spinner-cube {
|
||||
animation: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue