feat: replace loading spinners with skeletons

This commit is contained in:
2026-08-27 21:43:57 +03:00
parent 7d748a65d3
commit 71a214d7ad
15 changed files with 204 additions and 58 deletions
+18
View File
@@ -0,0 +1,18 @@
import { Stack } from "@mantine/core";
import type { FC, PropsWithChildren } from "react";
type LoadingSkeletonProps = PropsWithChildren<{
label: string;
}>;
const LoadingSkeleton: FC<LoadingSkeletonProps> = (props) => {
const { children, label } = props;
return (
<Stack aria-busy="true" aria-label={label} gap="md" role="status">
{children}
</Stack>
);
};
export { LoadingSkeleton };
+9 -5
View File
@@ -1,21 +1,25 @@
import { MantineProvider } from "@mantine/core";
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { LoadingSkeleton } from "./LoadingSkeleton";
import { StateView } from "./StateView";
describe("StateView", () => {
afterEach(cleanup);
it("renders an accessible loading state with a loader", () => {
it("renders an accessible structural loading region", () => {
render(
<MantineProvider>
<StateView loading text="Пожалуйста, подождите." title="Загрузка" />
<LoadingSkeleton label="Загрузка">
<div />
</LoadingSkeleton>
</MantineProvider>,
);
const status = screen.getByRole("status");
expect(status).toHaveTextContent("Загрузка");
expect(screen.getByLabelText("Загрузка")).toBeInTheDocument();
expect(screen.getByRole("status", { name: "Загрузка" })).toHaveAttribute(
"aria-busy",
"true",
);
});
it("renders an accessible error state", () => {
+3 -5
View File
@@ -1,22 +1,20 @@
import { Button, Loader, Paper, Stack, Text, Title } from "@mantine/core";
import { Button, Paper, Stack, Text, Title } from "@mantine/core";
import type { FC, ReactNode } from "react";
type StateViewProps = {
title: string;
text: string;
action?: ReactNode;
loading?: boolean;
error?: boolean;
};
const StateView: FC<StateViewProps> = (props) => {
const { title, text, action, loading = false, error = false } = props;
const role = loading ? "status" : error ? "alert" : undefined;
const { title, text, action, error = false } = props;
const role = error ? "alert" : undefined;
return (
<Paper p="xl" radius="md" shadow="xs" withBorder role={role}>
<Stack align="flex-start" gap="sm">
{loading ? <Loader aria-label={title} size="sm" /> : null}
<Title order={2}>{title}</Title>
<Text c="dimmed">{text}</Text>
{action}
+1
View File
@@ -1,2 +1,3 @@
export { AppShell } from "./AppShell";
export { LoadingSkeleton } from "./LoadingSkeleton";
export { Button, StateView } from "./StateView";