feat(ui): establish shared design system foundations

This commit is contained in:
2026-09-08 07:09:05 +03:00
parent 36698322b7
commit d762ba5034
18 changed files with 401 additions and 40 deletions
+58
View File
@@ -0,0 +1,58 @@
import { Button, type ButtonProps } from "@mantine/core";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { expect, fn, userEvent, within } from "storybook/test";
type ButtonStoryArgs = ButtonProps & { onClick: () => void };
const meta = {
title: "Design System/Components/Button",
component: Button,
tags: ["autodocs"],
args: {
children: "Выполнить пример",
onClick: fn(),
loading: false,
disabled: false,
variant: "filled",
},
argTypes: {
children: { control: "text" },
variant: { control: "select", options: ["filled", "default", "subtle"] },
loading: { control: "boolean" },
disabled: { control: "boolean" },
onClick: { control: false },
},
parameters: {
docs: {
description: {
component:
"Прямой Mantine Button с общей темой WealthLedger, без wrapper. Filled — основное действие; default — вторичное действие/retry; subtle — малозаметное вспомогательное действие. Radius md, primary blue и keyboard focus приходят из темы. Дайте кнопке доступное имя; loading означает mutation, disabled — недоступное действие. Loading/disabled не исполняют callback. Actions и interaction проверяют локальный callback, не API recovery.",
},
},
},
} satisfies Meta<ButtonStoryArgs>;
export default meta;
type Story = StoryObj<ButtonStoryArgs>;
export const Primary: Story = {
name: "Основное действие",
play: async ({ canvasElement, args }) => {
const button = within(canvasElement).getByRole("button", {
name: String(args.children),
});
button.focus();
await expect(button).toHaveFocus();
await userEvent.keyboard("{Enter}");
await expect(args.onClick).toHaveBeenCalled();
},
};
export const Secondary: Story = {
name: "Вторичное действие",
args: { variant: "default" },
};
export const Subtle: Story = {
name: "Вспомогательное действие",
args: { variant: "subtle" },
};
export const Loading: Story = { name: "Выполнение", args: { loading: true } };
export const Disabled: Story = { name: "Недоступно", args: { disabled: true } };
+35
View File
@@ -0,0 +1,35 @@
import { Paper, Skeleton } from "@mantine/core";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { LoadingSkeleton } from "./LoadingSkeleton";
const meta = {
title: "Design System/Components/LoadingSkeleton",
component: LoadingSkeleton,
tags: ["autodocs"],
args: { label: "Загрузка содержимого" },
argTypes: {
label: { control: "text" },
children: { table: { disable: true } },
},
parameters: {
docs: {
description: {
component:
"Общая status/busy область для initial или partial loading. Передайте доступную метку и только декоративные Skeleton shapes без интерактивных элементов. Геометрию определяет владелец section/page (см. Product/AccountPageLoading). При refetch сохраняйте уже видимые данные; для действия используйте Button loading. Этот пример не запускает запрос и не демонстрирует query lifecycle.",
},
},
},
render: (args) => (
<Paper p="md" withBorder>
<LoadingSkeleton {...args}>
<Skeleton h={24} w="60%" />
<Skeleton h={72} />
<Skeleton h={16} w="80%" />
</LoadingSkeleton>
</Paper>
),
} satisfies Meta<typeof LoadingSkeleton>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Initial: Story = { name: "Загрузка области" };
+1 -1
View File
@@ -10,7 +10,7 @@ type StateStoryArgs = ComponentProps<typeof StateView> & {
};
const meta = {
title: "WealthLedger/StateView",
title: "Design System/Components/StateView",
component: StateView,
tags: ["autodocs"],
args: {