mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c55829a39 | |||
| 2c3494c71e | |||
| fe059e7fc7 | |||
| 62c948fc91 |
@@ -0,0 +1,57 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import { Spinner } from "./Spinner";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Components/Spinner",
|
||||||
|
parameters: {
|
||||||
|
layout: "centered",
|
||||||
|
},
|
||||||
|
tags: ["autodocs"],
|
||||||
|
} satisfies Meta;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
const DeterminateSpinner = () => {
|
||||||
|
const [percentage, setPercentage] = useState(10);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => setPercentage(Math.min(100, percentage + 30)), 600);
|
||||||
|
}, [percentage]);
|
||||||
|
return <Spinner determinate value={percentage} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Determinate: Story = {
|
||||||
|
render: () => <DeterminateSpinner />,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IndeterminateSimple: Story = {
|
||||||
|
render: () => <Spinner variant="simple" />,
|
||||||
|
name: "Indeterminate (Simple)",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IndeterminateDynamic: Story = {
|
||||||
|
render: () => <Spinner variant="dynamic" />,
|
||||||
|
name: "Indeterminate (Dynamic)",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AllVariants: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="flex flex-col gap-4 items-center">
|
||||||
|
<div className="flex gap-4 items-center">
|
||||||
|
<Spinner variant="simple" />
|
||||||
|
<span>Simple Indeterminate</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-4 items-center">
|
||||||
|
<Spinner variant="dynamic" />
|
||||||
|
<span>Dynamic Indeterminate</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-4 items-center">
|
||||||
|
<DeterminateSpinner />
|
||||||
|
<span>Determinate</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import type { HTMLProps } from "../../shared/types";
|
||||||
|
import { cn } from "../../shared/utils/cn";
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
type BaseSpinnerProps = HTMLProps<"svg">;
|
||||||
|
|
||||||
|
export type DeterminateSpinnerProps = BaseSpinnerProps & {
|
||||||
|
determinate: true;
|
||||||
|
value: number;
|
||||||
|
variant?: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IndeterminateSpinnerProps = BaseSpinnerProps & {
|
||||||
|
determinate?: false | null | undefined;
|
||||||
|
value?: never;
|
||||||
|
variant?: "simple" | "dynamic";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SpinnerProps = DeterminateSpinnerProps | IndeterminateSpinnerProps;
|
||||||
|
|
||||||
|
const SIZE = 48;
|
||||||
|
const STROKE_WIDTH = 6;
|
||||||
|
const radius = (SIZE - STROKE_WIDTH) / 2;
|
||||||
|
const circumference = 2 * Math.PI * radius;
|
||||||
|
|
||||||
|
export const Spinner = ({
|
||||||
|
value = 10,
|
||||||
|
determinate = false,
|
||||||
|
variant = "simple",
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: SpinnerProps) => {
|
||||||
|
const offset = useMemo(
|
||||||
|
() => circumference - (value / 100) * circumference,
|
||||||
|
[value]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg width={SIZE} height={SIZE} className={className} {...props}>
|
||||||
|
<circle
|
||||||
|
cx={SIZE / 2}
|
||||||
|
cy={SIZE / 2}
|
||||||
|
r={radius}
|
||||||
|
fill="none"
|
||||||
|
className="stroke-grey-970"
|
||||||
|
strokeWidth={STROKE_WIDTH}
|
||||||
|
/>
|
||||||
|
{determinate ? (
|
||||||
|
<g>
|
||||||
|
<circle
|
||||||
|
cx={SIZE / 2}
|
||||||
|
cy={SIZE / 2}
|
||||||
|
r={radius}
|
||||||
|
fill="none"
|
||||||
|
className="stroke-primary-500 animate-determinate-spinner"
|
||||||
|
strokeWidth={STROKE_WIDTH}
|
||||||
|
strokeDasharray={circumference}
|
||||||
|
strokeDashoffset={offset}
|
||||||
|
strokeLinecap="round"
|
||||||
|
transform={`rotate(-90 ${SIZE / 2} ${SIZE / 2})`}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
) : variant === "simple" ? (
|
||||||
|
<g className="animate-indeterminate-spinner origin-center">
|
||||||
|
<circle
|
||||||
|
cx={SIZE / 2}
|
||||||
|
cy={SIZE / 2}
|
||||||
|
r={radius}
|
||||||
|
fill="none"
|
||||||
|
className="stroke-primary-500"
|
||||||
|
strokeWidth={STROKE_WIDTH}
|
||||||
|
strokeDasharray={circumference}
|
||||||
|
strokeDashoffset={circumference * 0.75}
|
||||||
|
strokeLinecap="round"
|
||||||
|
transform={`rotate(-90 ${SIZE / 2} ${SIZE / 2})`}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
) : (
|
||||||
|
<g className="animate-spinner-rotate origin-center">
|
||||||
|
<circle
|
||||||
|
cx={SIZE / 2}
|
||||||
|
cy={SIZE / 2}
|
||||||
|
r={radius}
|
||||||
|
fill="none"
|
||||||
|
className="stroke-primary-500 animate-arc-length"
|
||||||
|
strokeWidth={STROKE_WIDTH}
|
||||||
|
strokeDasharray={circumference}
|
||||||
|
strokeDashoffset={circumference * 0.75}
|
||||||
|
strokeLinecap="round"
|
||||||
|
transform={`rotate(-90 ${SIZE / 2} ${SIZE / 2})`}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
@layer utilities {
|
||||||
|
@keyframes spinner-rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(270deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes arc-length-change {
|
||||||
|
0% {
|
||||||
|
stroke-dashoffset: 85%;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
stroke-dashoffset: 25%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stroke-dashoffset: 10%;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
stroke-dashoffset: 25%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: 85%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-indeterminate-spinner {
|
||||||
|
animation: spinner-rotate 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spinner-rotate {
|
||||||
|
animation: spinner-rotate 1.8s cubic-bezier(0.65, 0, 0.35, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-arc-length {
|
||||||
|
animation: arc-length-change 1.8s cubic-bezier(0.65, 0, 0.35, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-determinate-spinner {
|
||||||
|
transition: stroke-dashoffset 0.3s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user