, string> = {
- sm: "h-5 w-5 border-2",
- md: "h-8 w-8 border-2",
- lg: "h-12 w-12 border-[3px]",
+ sm: "loading-spinner--sm",
+ md: "loading-spinner--md",
+ lg: "loading-spinner--lg",
};
export function LoadingSpinner({
size = "md",
message,
+ showMessage = false,
className = "",
}: LoadingSpinnerProps) {
+ const label = message ?? "Loading";
+
return (
-
- {message &&
{message}
}
-
{message ?? "Loading"}
+
+ {showMessage && message && (
+
{message}
+ )}
+
{label}
);
}
diff --git a/components/__tests__/ColorContrast.test.tsx b/components/__tests__/ColorContrast.test.tsx
index ceda564..27baff6 100644
--- a/components/__tests__/ColorContrast.test.tsx
+++ b/components/__tests__/ColorContrast.test.tsx
@@ -86,7 +86,7 @@ describe("Color Contrast Compliance (Req 18.3)", () => {
describe("LoadingSpinner", () => {
it("uses readable stone text (not text-gray-400) for message text in dark mode", () => {
const { container } = render(
-
+
);
const messageEl = container.querySelector("p");
expect(messageEl).not.toBeNull();
diff --git a/components/__tests__/LoadingSpinner.test.tsx b/components/__tests__/LoadingSpinner.test.tsx
index cde5fd0..fafad43 100644
--- a/components/__tests__/LoadingSpinner.test.tsx
+++ b/components/__tests__/LoadingSpinner.test.tsx
@@ -24,33 +24,36 @@ describe("LoadingSpinner", () => {
expect(screen.getByText("Loading")).toBeDefined(); // sr-only text
});
- it("should display a custom message below the spinner", () => {
+ it("should display a custom message for screen readers only by default", () => {
render();
- const messages = screen.getAllByText("Fetching data...");
- expect(messages.length).toBe(2); // visible + sr-only
+ expect(screen.getAllByText("Fetching data...")).toHaveLength(1);
+ expect(screen.queryByRole("paragraph")).toBeNull();
const spinner = screen.getByTestId("loading-spinner");
expect(spinner.getAttribute("aria-label")).toBe("Fetching data...");
});
+ it("should show visible message when showMessage is true", () => {
+ render();
+ expect(screen.getAllByText("Fetching data...")).toHaveLength(2);
+ });
+
it("should render small size variant", () => {
const { container } = render();
- const spinnerCircle = container.querySelector(".animate-spin");
- expect(spinnerCircle?.classList.contains("h-5")).toBe(true);
- expect(spinnerCircle?.classList.contains("w-5")).toBe(true);
+ const arcSpinner = container.querySelector(".loading-spinner");
+ expect(arcSpinner?.classList.contains("loading-spinner--sm")).toBe(true);
+ expect(container.querySelectorAll(".loading-spinner__arc")).toHaveLength(3);
});
it("should render medium size variant (default)", () => {
const { container } = render();
- const spinnerCircle = container.querySelector(".animate-spin");
- expect(spinnerCircle?.classList.contains("h-8")).toBe(true);
- expect(spinnerCircle?.classList.contains("w-8")).toBe(true);
+ const arcSpinner = container.querySelector(".loading-spinner");
+ expect(arcSpinner?.classList.contains("loading-spinner--md")).toBe(true);
});
it("should render large size variant", () => {
const { container } = render();
- const spinnerCircle = container.querySelector(".animate-spin");
- expect(spinnerCircle?.classList.contains("h-12")).toBe(true);
- expect(spinnerCircle?.classList.contains("w-12")).toBe(true);
+ const arcSpinner = container.querySelector(".loading-spinner");
+ expect(arcSpinner?.classList.contains("loading-spinner--lg")).toBe(true);
});
it("should apply additional className", () => {
@@ -60,7 +63,7 @@ describe("LoadingSpinner", () => {
});
it("should include sr-only text for screen readers", () => {
- render();
+ render();
const srOnly = screen.getByText("Loading chart", {
selector: ".sr-only",
});
diff --git a/package.json b/package.json
index 1bfb2c0..c054a62 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "the-open-stock",
- "version": "0.66.0",
+ "version": "0.67.0",
"private": true,
"scripts": {
"dev": "bun --bun next dev",