refactor: establish local frontend workspace boundaries
Conventional commits / commits (pull_request) Successful in 24s
Frontend security / security (pull_request) Successful in 9s
Frontend verify / verify (pull_request) Successful in 5m18s
Frontend security / security (push) Successful in 10s
Frontend verify / verify (push) Successful in 6m45s
Conventional commits / commits (pull_request) Successful in 24s
Frontend security / security (pull_request) Successful in 9s
Frontend verify / verify (pull_request) Successful in 5m18s
Frontend security / security (push) Successful in 10s
Frontend verify / verify (push) Successful in 6m45s
OpenSpec change: evolve-frontend-stack
This commit was merged in pull request #34.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { AddIcon, DeleteIcon, EditIcon } from "./index";
|
||||
|
||||
const meta = {
|
||||
title: "Design System/Icons",
|
||||
component: AddIcon,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Статичные SVG: size по умолчанию 24, strokeWidth 1.5, цвет currentColor. В кнопке иконка декоративная, имя принадлежит действию. Для самостоятельного изображения задайте aria-hidden={false}, role=img и aria-label либо aria-labelledby. Геометрия взята из Card/Cards; подписи и действия остаются у приложения.",
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof AddIcon>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Decorative: Story = {
|
||||
render: () => (
|
||||
<div style={{ display: "flex", gap: 16, color: "#2563eb" }}>
|
||||
<button type="button" aria-label="Добавить пример">
|
||||
<AddIcon size={20} strokeWidth={2} />
|
||||
</button>
|
||||
<button type="button" aria-label="Изменить пример">
|
||||
<EditIcon size={18} />
|
||||
</button>
|
||||
<button type="button" aria-label="Удалить пример">
|
||||
<DeleteIcon size={18} />
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const Meaningful: Story = {
|
||||
render: () => (
|
||||
<div style={{ display: "flex", gap: 16, color: "#2563eb" }}>
|
||||
<AddIcon aria-hidden={false} role="img" aria-label="Добавление" />
|
||||
<EditIcon aria-hidden={false} role="img" aria-label="Редактирование" />
|
||||
<DeleteIcon aria-hidden={false} role="img" aria-label="Удаление" />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { AddIcon, DeleteIcon, EditIcon } from "./index";
|
||||
|
||||
describe("icon accessibility", () => {
|
||||
it.each([AddIcon, EditIcon, DeleteIcon])(
|
||||
"keeps decoration hidden and exposes explicitly named images: %s",
|
||||
(Icon) => {
|
||||
const { rerender } = render(
|
||||
<button type="button" aria-label="Действие">
|
||||
<Icon />
|
||||
</button>,
|
||||
);
|
||||
expect(screen.queryByRole("img")).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Действие" }).querySelector("svg"),
|
||||
).toHaveAttribute("focusable", "false");
|
||||
rerender(
|
||||
<Icon
|
||||
aria-hidden={false}
|
||||
role="img"
|
||||
aria-label="Пример"
|
||||
size={18}
|
||||
strokeWidth={2}
|
||||
/>,
|
||||
);
|
||||
const icon = screen.getByRole("img", { name: "Пример" });
|
||||
expect(icon).toHaveAttribute("width", "18");
|
||||
expect(icon).toHaveAttribute("height", "18");
|
||||
expect(icon).toHaveAttribute("stroke-width", "2");
|
||||
expect(icon).toHaveAttribute("stroke", "currentColor");
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
export { AddIcon } from "./svg/AddIcon";
|
||||
export { DeleteIcon } from "./svg/DeleteIcon";
|
||||
export { EditIcon } from "./svg/EditIcon";
|
||||
export type { IconProps } from "./types";
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { FC } from "react";
|
||||
import type { IconProps } from "../types";
|
||||
|
||||
const AddIcon: FC<IconProps> = (props) => {
|
||||
const { size = "24", ...svgProps } = props;
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
{...svgProps}
|
||||
>
|
||||
<path d="M12 5v14 M5 12h14" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export { AddIcon };
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { FC } from "react";
|
||||
import type { IconProps } from "../types";
|
||||
|
||||
const DeleteIcon: FC<IconProps> = (props) => {
|
||||
const { size = "24", ...svgProps } = props;
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
{...svgProps}
|
||||
>
|
||||
<path d="M3 6h18 M9 6V3h6v3 M5 6l1 15h12l1-15 M10 10v7 M14 10v7" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export { DeleteIcon };
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { FC } from "react";
|
||||
import type { IconProps } from "../types";
|
||||
|
||||
const EditIcon: FC<IconProps> = (props) => {
|
||||
const { size = "24", ...svgProps } = props;
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
{...svgProps}
|
||||
>
|
||||
<path d="m16 3 5 5-12 12-6 1 1-6L16 3Z M13 6l5 5" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export { EditIcon };
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
type IconProps = Omit<
|
||||
ComponentPropsWithoutRef<"svg">,
|
||||
"children" | "dangerouslySetInnerHTML"
|
||||
> & {
|
||||
size?: number | string;
|
||||
};
|
||||
|
||||
export type { IconProps };
|
||||
Reference in New Issue
Block a user