refactor(ui): convert Icon and Badge components to JSX syntax
- Convert Icon component from createElement to JSX (Icon.tsx) - Convert Icon tests to JSX (24 tests passing) - Convert Icon stories to JSX (Icon.stories.tsx) - Convert Badge component from createElement to JSX (Badge.tsx) - Convert Badge tests to JSX (34 tests passing) - Rename files from .ts to .tsx - Remove createElement imports, use native JSX syntax - All tests passing (58/58) Part of broader effort to convert entire codebase from createElement to JSX. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9a8c0beb85
commit
b17349c071
5 changed files with 254 additions and 392 deletions
62
packages/storybook/stories/Icon.stories.tsx
Normal file
62
packages/storybook/stories/Icon.stories.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { Icon } from "@tpmjs/ui/Icon/Icon";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Components/Icon",
|
||||||
|
component: Icon,
|
||||||
|
tags: ["autodocs"],
|
||||||
|
parameters: {
|
||||||
|
layout: "centered",
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof Icon>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const AllIcons: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="grid grid-cols-4 gap-8 p-8">
|
||||||
|
{(
|
||||||
|
[
|
||||||
|
"copy",
|
||||||
|
"externalLink",
|
||||||
|
"github",
|
||||||
|
"check",
|
||||||
|
"x",
|
||||||
|
"chevronDown",
|
||||||
|
"sun",
|
||||||
|
"moon",
|
||||||
|
] as const
|
||||||
|
).map((iconName) => (
|
||||||
|
<div key={iconName} className="flex flex-col items-center gap-2">
|
||||||
|
<Icon icon={iconName} size="lg" />
|
||||||
|
<span className="text-sm text-foreground-secondary">{iconName}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AllSizes: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
|
{(["sm", "md", "lg"] as const).map((size) => (
|
||||||
|
<div key={size} className="flex flex-col items-center gap-2">
|
||||||
|
<Icon icon="github" size={size} />
|
||||||
|
<span className="text-sm text-foreground-secondary">{size}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithColors: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
|
<Icon icon="check" size="lg" className="text-success" />
|
||||||
|
<Icon icon="x" size="lg" className="text-error" />
|
||||||
|
<Icon icon="github" size="lg" className="text-info" />
|
||||||
|
<Icon icon="sun" size="lg" className="text-warning" />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import { createElement } from "react";
|
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { Badge } from "./Badge";
|
import { Badge } from "./Badge";
|
||||||
|
|
||||||
describe("Badge", () => {
|
describe("Badge", () => {
|
||||||
describe("Rendering", () => {
|
describe("Rendering", () => {
|
||||||
it("renders a badge element", () => {
|
it("renders a badge element", () => {
|
||||||
render(createElement(Badge, { "data-testid": "badge" }, "Badge"));
|
render(<Badge data-testid="badge">Badge</Badge>);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge).toBeInTheDocument();
|
expect(badge).toBeInTheDocument();
|
||||||
expect(badge.tagName).toBe("DIV");
|
expect(badge.tagName).toBe("DIV");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders children text", () => {
|
it("renders children text", () => {
|
||||||
render(createElement(Badge, null, "Active"));
|
render(<Badge>Active</Badge>);
|
||||||
expect(screen.getByText("Active")).toBeInTheDocument();
|
expect(screen.getByText("Active")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -21,14 +20,9 @@ describe("Badge", () => {
|
||||||
describe("Variants", () => {
|
describe("Variants", () => {
|
||||||
it("applies default variant classes", () => {
|
it("applies default variant classes", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge variant="default" data-testid="badge">
|
||||||
Badge,
|
Default
|
||||||
{
|
</Badge>,
|
||||||
variant: "default",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Default",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("bg-primary");
|
expect(badge.className).toContain("bg-primary");
|
||||||
|
|
@ -38,14 +32,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("applies secondary variant classes", () => {
|
it("applies secondary variant classes", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge variant="secondary" data-testid="badge">
|
||||||
Badge,
|
Secondary
|
||||||
{
|
</Badge>,
|
||||||
variant: "secondary",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Secondary",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("bg-secondary");
|
expect(badge.className).toContain("bg-secondary");
|
||||||
|
|
@ -54,14 +43,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("applies outline variant classes", () => {
|
it("applies outline variant classes", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge variant="outline" data-testid="badge">
|
||||||
Badge,
|
Outline
|
||||||
{
|
</Badge>,
|
||||||
variant: "outline",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Outline",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("bg-transparent");
|
expect(badge.className).toContain("bg-transparent");
|
||||||
|
|
@ -71,14 +55,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("applies success variant classes", () => {
|
it("applies success variant classes", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge variant="success" data-testid="badge">
|
||||||
Badge,
|
Success
|
||||||
{
|
</Badge>,
|
||||||
variant: "success",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Success",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("bg-success");
|
expect(badge.className).toContain("bg-success");
|
||||||
|
|
@ -87,14 +66,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("applies error variant classes", () => {
|
it("applies error variant classes", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge variant="error" data-testid="badge">
|
||||||
Badge,
|
Error
|
||||||
{
|
</Badge>,
|
||||||
variant: "error",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Error",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("bg-error");
|
expect(badge.className).toContain("bg-error");
|
||||||
|
|
@ -103,14 +77,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("applies warning variant classes", () => {
|
it("applies warning variant classes", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge variant="warning" data-testid="badge">
|
||||||
Badge,
|
Warning
|
||||||
{
|
</Badge>,
|
||||||
variant: "warning",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Warning",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("bg-warning");
|
expect(badge.className).toContain("bg-warning");
|
||||||
|
|
@ -119,14 +88,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("applies info variant classes", () => {
|
it("applies info variant classes", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge variant="info" data-testid="badge">
|
||||||
Badge,
|
Info
|
||||||
{
|
</Badge>,
|
||||||
variant: "info",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Info",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("bg-info");
|
expect(badge.className).toContain("bg-info");
|
||||||
|
|
@ -137,14 +101,9 @@ describe("Badge", () => {
|
||||||
describe("Sizes", () => {
|
describe("Sizes", () => {
|
||||||
it("applies small size classes", () => {
|
it("applies small size classes", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge size="sm" data-testid="badge">
|
||||||
Badge,
|
Small
|
||||||
{
|
</Badge>,
|
||||||
size: "sm",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Small",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("px-2");
|
expect(badge.className).toContain("px-2");
|
||||||
|
|
@ -154,14 +113,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("applies medium size classes (default)", () => {
|
it("applies medium size classes (default)", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge size="md" data-testid="badge">
|
||||||
Badge,
|
Medium
|
||||||
{
|
</Badge>,
|
||||||
size: "md",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Medium",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("px-2.5");
|
expect(badge.className).toContain("px-2.5");
|
||||||
|
|
@ -171,14 +125,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("applies large size classes", () => {
|
it("applies large size classes", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge size="lg" data-testid="badge">
|
||||||
Badge,
|
Large
|
||||||
{
|
</Badge>,
|
||||||
size: "lg",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Large",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("px-3");
|
expect(badge.className).toContain("px-3");
|
||||||
|
|
@ -187,15 +136,7 @@ describe("Badge", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses medium size by default", () => {
|
it("uses medium size by default", () => {
|
||||||
render(
|
render(<Badge data-testid="badge">Default</Badge>);
|
||||||
createElement(
|
|
||||||
Badge,
|
|
||||||
{
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Default",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("text-sm");
|
expect(badge.className).toContain("text-sm");
|
||||||
});
|
});
|
||||||
|
|
@ -204,14 +145,9 @@ describe("Badge", () => {
|
||||||
describe("HTML Attributes", () => {
|
describe("HTML Attributes", () => {
|
||||||
it("passes through id attribute", () => {
|
it("passes through id attribute", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge id="badge-id" data-testid="badge">
|
||||||
Badge,
|
Badge
|
||||||
{
|
</Badge>,
|
||||||
id: "badge-id",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Badge",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge).toHaveAttribute("id", "badge-id");
|
expect(badge).toHaveAttribute("id", "badge-id");
|
||||||
|
|
@ -219,14 +155,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("passes through title attribute", () => {
|
it("passes through title attribute", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge title="Badge tooltip" data-testid="badge">
|
||||||
Badge,
|
Badge
|
||||||
{
|
</Badge>,
|
||||||
title: "Badge tooltip",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Badge",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge).toHaveAttribute("title", "Badge tooltip");
|
expect(badge).toHaveAttribute("title", "Badge tooltip");
|
||||||
|
|
@ -235,16 +166,14 @@ describe("Badge", () => {
|
||||||
it("passes through onClick handler", () => {
|
it("passes through onClick handler", () => {
|
||||||
let clicked = false;
|
let clicked = false;
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge
|
||||||
Badge,
|
onClick={() => {
|
||||||
{
|
clicked = true;
|
||||||
onClick: () => {
|
}}
|
||||||
clicked = true;
|
data-testid="badge"
|
||||||
},
|
>
|
||||||
"data-testid": "badge",
|
Clickable
|
||||||
},
|
</Badge>,
|
||||||
"Clickable",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
badge.click();
|
badge.click();
|
||||||
|
|
@ -253,14 +182,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("passes through role attribute", () => {
|
it("passes through role attribute", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge role="status" data-testid="badge">
|
||||||
Badge,
|
Status
|
||||||
{
|
</Badge>,
|
||||||
role: "status",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Status",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge).toHaveAttribute("role", "status");
|
expect(badge).toHaveAttribute("role", "status");
|
||||||
|
|
@ -270,14 +194,9 @@ describe("Badge", () => {
|
||||||
describe("ARIA Attributes", () => {
|
describe("ARIA Attributes", () => {
|
||||||
it("passes through aria-label", () => {
|
it("passes through aria-label", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge aria-label="Status badge" data-testid="badge">
|
||||||
Badge,
|
Active
|
||||||
{
|
</Badge>,
|
||||||
"aria-label": "Status badge",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Active",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge).toHaveAttribute("aria-label", "Status badge");
|
expect(badge).toHaveAttribute("aria-label", "Status badge");
|
||||||
|
|
@ -285,14 +204,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("passes through aria-describedby", () => {
|
it("passes through aria-describedby", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge aria-describedby="description-id" data-testid="badge">
|
||||||
Badge,
|
Badge
|
||||||
{
|
</Badge>,
|
||||||
"aria-describedby": "description-id",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Badge",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge).toHaveAttribute("aria-describedby", "description-id");
|
expect(badge).toHaveAttribute("aria-describedby", "description-id");
|
||||||
|
|
@ -300,14 +214,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("passes through aria-live for dynamic status", () => {
|
it("passes through aria-live for dynamic status", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge aria-live="polite" data-testid="badge">
|
||||||
Badge,
|
Updating
|
||||||
{
|
</Badge>,
|
||||||
"aria-live": "polite",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Updating",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge).toHaveAttribute("aria-live", "polite");
|
expect(badge).toHaveAttribute("aria-live", "polite");
|
||||||
|
|
@ -317,14 +226,9 @@ describe("Badge", () => {
|
||||||
describe("Custom className", () => {
|
describe("Custom className", () => {
|
||||||
it("merges custom className with variant classes", () => {
|
it("merges custom className with variant classes", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge className="custom-badge" data-testid="badge">
|
||||||
Badge,
|
Custom
|
||||||
{
|
</Badge>,
|
||||||
className: "custom-badge",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Custom",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("custom-badge");
|
expect(badge.className).toContain("custom-badge");
|
||||||
|
|
@ -337,15 +241,13 @@ describe("Badge", () => {
|
||||||
it("forwards ref to badge element", () => {
|
it("forwards ref to badge element", () => {
|
||||||
let ref: HTMLDivElement | null = null;
|
let ref: HTMLDivElement | null = null;
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge
|
||||||
Badge,
|
ref={(el: HTMLDivElement | null) => {
|
||||||
{
|
ref = el;
|
||||||
ref: (el: HTMLDivElement | null) => {
|
}}
|
||||||
ref = el;
|
>
|
||||||
},
|
Badge
|
||||||
},
|
</Badge>,
|
||||||
"Badge",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(HTMLDivElement);
|
expect(ref).toBeInstanceOf(HTMLDivElement);
|
||||||
expect(ref?.tagName).toBe("DIV");
|
expect(ref?.tagName).toBe("DIV");
|
||||||
|
|
@ -354,15 +256,7 @@ describe("Badge", () => {
|
||||||
|
|
||||||
describe("Base Classes", () => {
|
describe("Base Classes", () => {
|
||||||
it("always includes base classes", () => {
|
it("always includes base classes", () => {
|
||||||
render(
|
render(<Badge data-testid="badge">Badge</Badge>);
|
||||||
createElement(
|
|
||||||
Badge,
|
|
||||||
{
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Badge",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("inline-flex");
|
expect(badge.className).toContain("inline-flex");
|
||||||
expect(badge.className).toContain("items-center");
|
expect(badge.className).toContain("items-center");
|
||||||
|
|
@ -372,15 +266,7 @@ describe("Badge", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes transition classes", () => {
|
it("includes transition classes", () => {
|
||||||
render(
|
render(<Badge data-testid="badge">Badge</Badge>);
|
||||||
createElement(
|
|
||||||
Badge,
|
|
||||||
{
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Badge",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("transition-base");
|
expect(badge.className).toContain("transition-base");
|
||||||
});
|
});
|
||||||
|
|
@ -389,15 +275,9 @@ describe("Badge", () => {
|
||||||
describe("Compound Scenarios", () => {
|
describe("Compound Scenarios", () => {
|
||||||
it("works correctly with success variant and small size", () => {
|
it("works correctly with success variant and small size", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge variant="success" size="sm" data-testid="badge">
|
||||||
Badge,
|
Success
|
||||||
{
|
</Badge>,
|
||||||
variant: "success",
|
|
||||||
size: "sm",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Success",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("bg-success");
|
expect(badge.className).toContain("bg-success");
|
||||||
|
|
@ -406,15 +286,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("works correctly with error variant and large size", () => {
|
it("works correctly with error variant and large size", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge variant="error" size="lg" data-testid="badge">
|
||||||
Badge,
|
Error
|
||||||
{
|
</Badge>,
|
||||||
variant: "error",
|
|
||||||
size: "lg",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"Error",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("bg-error");
|
expect(badge.className).toContain("bg-error");
|
||||||
|
|
@ -423,15 +297,13 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("works correctly with outline variant and custom className", () => {
|
it("works correctly with outline variant and custom className", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge
|
||||||
Badge,
|
variant="outline"
|
||||||
{
|
className="hover:bg-accent"
|
||||||
variant: "outline",
|
data-testid="badge"
|
||||||
className: "hover:bg-accent",
|
>
|
||||||
"data-testid": "badge",
|
Outlined
|
||||||
},
|
</Badge>,
|
||||||
"Outlined",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("bg-transparent");
|
expect(badge.className).toContain("bg-transparent");
|
||||||
|
|
@ -441,17 +313,15 @@ describe("Badge", () => {
|
||||||
it("works correctly with warning variant and onClick", () => {
|
it("works correctly with warning variant and onClick", () => {
|
||||||
let clicked = false;
|
let clicked = false;
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge
|
||||||
Badge,
|
variant="warning"
|
||||||
{
|
onClick={() => {
|
||||||
variant: "warning",
|
clicked = true;
|
||||||
onClick: () => {
|
}}
|
||||||
clicked = true;
|
data-testid="badge"
|
||||||
},
|
>
|
||||||
"data-testid": "badge",
|
Warning
|
||||||
},
|
</Badge>,
|
||||||
"Warning",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("bg-warning");
|
expect(badge.className).toContain("bg-warning");
|
||||||
|
|
@ -462,23 +332,20 @@ describe("Badge", () => {
|
||||||
|
|
||||||
describe("Content Types", () => {
|
describe("Content Types", () => {
|
||||||
it("renders with text content", () => {
|
it("renders with text content", () => {
|
||||||
render(createElement(Badge, null, "Text"));
|
render(<Badge>Text</Badge>);
|
||||||
expect(screen.getByText("Text")).toBeInTheDocument();
|
expect(screen.getByText("Text")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders with numeric content", () => {
|
it("renders with numeric content", () => {
|
||||||
render(createElement(Badge, null, "42"));
|
render(<Badge>42</Badge>);
|
||||||
expect(screen.getByText("42")).toBeInTheDocument();
|
expect(screen.getByText("42")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders with multiple children", () => {
|
it("renders with multiple children", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge>
|
||||||
Badge,
|
Count: <strong>5</strong>
|
||||||
null,
|
</Badge>,
|
||||||
"Count: ",
|
|
||||||
createElement("strong", null, "5"),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
expect(screen.getByText("Count:")).toBeInTheDocument();
|
expect(screen.getByText("Count:")).toBeInTheDocument();
|
||||||
expect(screen.getByText("5")).toBeInTheDocument();
|
expect(screen.getByText("5")).toBeInTheDocument();
|
||||||
|
|
@ -488,16 +355,14 @@ describe("Badge", () => {
|
||||||
describe("Semantic Usage", () => {
|
describe("Semantic Usage", () => {
|
||||||
it("can be used as status indicator with role", () => {
|
it("can be used as status indicator with role", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge
|
||||||
Badge,
|
variant="success"
|
||||||
{
|
role="status"
|
||||||
variant: "success",
|
aria-label="Online status"
|
||||||
role: "status",
|
data-testid="badge"
|
||||||
"aria-label": "Online status",
|
>
|
||||||
"data-testid": "badge",
|
Online
|
||||||
},
|
</Badge>,
|
||||||
"Online",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge).toHaveAttribute("role", "status");
|
expect(badge).toHaveAttribute("role", "status");
|
||||||
|
|
@ -507,16 +372,14 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("can be used as notification count", () => {
|
it("can be used as notification count", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge
|
||||||
Badge,
|
variant="error"
|
||||||
{
|
size="sm"
|
||||||
variant: "error",
|
aria-label="3 unread messages"
|
||||||
size: "sm",
|
data-testid="badge"
|
||||||
"aria-label": "3 unread messages",
|
>
|
||||||
"data-testid": "badge",
|
3
|
||||||
},
|
</Badge>,
|
||||||
"3",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge).toHaveAttribute("aria-label", "3 unread messages");
|
expect(badge).toHaveAttribute("aria-label", "3 unread messages");
|
||||||
|
|
@ -525,14 +388,9 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("can be used as tag/label", () => {
|
it("can be used as tag/label", () => {
|
||||||
render(
|
render(
|
||||||
createElement(
|
<Badge variant="outline" data-testid="badge">
|
||||||
Badge,
|
TypeScript
|
||||||
{
|
</Badge>,
|
||||||
variant: "outline",
|
|
||||||
"data-testid": "badge",
|
|
||||||
},
|
|
||||||
"TypeScript",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const badge = screen.getByTestId("badge");
|
const badge = screen.getByTestId("badge");
|
||||||
expect(badge.className).toContain("bg-transparent");
|
expect(badge.className).toContain("bg-transparent");
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { cn } from "@tpmjs/utils/cn";
|
import { cn } from "@tpmjs/utils/cn";
|
||||||
import { createElement, forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
import type { BadgeProps } from "./types";
|
import type { BadgeProps } from "./types";
|
||||||
import { badgeVariants } from "./variants";
|
import { badgeVariants } from "./variants";
|
||||||
|
|
||||||
|
|
@ -7,34 +7,31 @@ import { badgeVariants } from "./variants";
|
||||||
* Badge component
|
* Badge component
|
||||||
*
|
*
|
||||||
* A versatile badge component for status indicators, tags, and labels.
|
* A versatile badge component for status indicators, tags, and labels.
|
||||||
* Built with .ts-only React using createElement.
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```tsx
|
||||||
* import { Badge } from '@tpmjs/ui/Badge/Badge';
|
* import { Badge } from '@tpmjs/ui/Badge/Badge';
|
||||||
* import { createElement } from 'react';
|
|
||||||
*
|
*
|
||||||
* function MyComponent() {
|
* function MyComponent() {
|
||||||
* return createElement(Badge, {
|
* return <Badge variant="success">Active</Badge>;
|
||||||
* variant: 'success',
|
|
||||||
* children: 'Active',
|
|
||||||
* });
|
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const Badge = forwardRef<HTMLDivElement, BadgeProps>(
|
export const Badge = forwardRef<HTMLDivElement, BadgeProps>(
|
||||||
({ className, variant = "default", size = "md", ...props }, ref) => {
|
({ className, variant = "default", size = "md", ...props }, ref) => {
|
||||||
return createElement("div", {
|
return (
|
||||||
className: cn(
|
<div
|
||||||
badgeVariants({
|
ref={ref}
|
||||||
variant,
|
className={cn(
|
||||||
size,
|
badgeVariants({
|
||||||
}),
|
variant,
|
||||||
className,
|
size,
|
||||||
),
|
}),
|
||||||
ref,
|
className,
|
||||||
...props,
|
)}
|
||||||
});
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1,38 +1,37 @@
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import { createElement } from "react";
|
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { Icon } from "./Icon";
|
import { Icon } from "./Icon";
|
||||||
|
|
||||||
describe("Icon", () => {
|
describe("Icon", () => {
|
||||||
describe("Rendering", () => {
|
describe("Rendering", () => {
|
||||||
it("renders an SVG element", () => {
|
it("renders an SVG element", () => {
|
||||||
render(createElement(Icon, { icon: "check", "data-testid": "icon" }));
|
render(<Icon icon="check" data-testid="icon" />);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon).toBeInTheDocument();
|
expect(icon).toBeInTheDocument();
|
||||||
expect(icon.tagName).toBe("svg");
|
expect(icon.tagName).toBe("svg");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders with correct viewBox", () => {
|
it("renders with correct viewBox", () => {
|
||||||
render(createElement(Icon, { icon: "check", "data-testid": "icon" }));
|
render(<Icon icon="check" data-testid="icon" />);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon).toHaveAttribute("viewBox", "0 0 24 24");
|
expect(icon).toHaveAttribute("viewBox", "0 0 24 24");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders with currentColor fill", () => {
|
it("renders with currentColor fill", () => {
|
||||||
render(createElement(Icon, { icon: "check", "data-testid": "icon" }));
|
render(<Icon icon="check" data-testid="icon" />);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon).toHaveAttribute("fill", "currentColor");
|
expect(icon).toHaveAttribute("fill", "currentColor");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes path element", () => {
|
it("includes path element", () => {
|
||||||
render(createElement(Icon, { icon: "check", "data-testid": "icon" }));
|
render(<Icon icon="check" data-testid="icon" />);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
const path = icon.querySelector("path");
|
const path = icon.querySelector("path");
|
||||||
expect(path).toBeInTheDocument();
|
expect(path).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is aria-hidden by default", () => {
|
it("is aria-hidden by default", () => {
|
||||||
render(createElement(Icon, { icon: "check", "data-testid": "icon" }));
|
render(<Icon icon="check" data-testid="icon" />);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon).toHaveAttribute("aria-hidden", "true");
|
expect(icon).toHaveAttribute("aria-hidden", "true");
|
||||||
});
|
});
|
||||||
|
|
@ -40,46 +39,28 @@ describe("Icon", () => {
|
||||||
|
|
||||||
describe("Size Variants", () => {
|
describe("Size Variants", () => {
|
||||||
it("applies small size", () => {
|
it("applies small size", () => {
|
||||||
render(
|
render(<Icon icon="check" size="sm" data-testid="icon" />);
|
||||||
createElement(Icon, {
|
|
||||||
icon: "check",
|
|
||||||
size: "sm",
|
|
||||||
"data-testid": "icon",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon.className).toContain("w-4");
|
expect(icon.className).toContain("w-4");
|
||||||
expect(icon.className).toContain("h-4");
|
expect(icon.className).toContain("h-4");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies medium size", () => {
|
it("applies medium size", () => {
|
||||||
render(
|
render(<Icon icon="check" size="md" data-testid="icon" />);
|
||||||
createElement(Icon, {
|
|
||||||
icon: "check",
|
|
||||||
size: "md",
|
|
||||||
"data-testid": "icon",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon.className).toContain("w-5");
|
expect(icon.className).toContain("w-5");
|
||||||
expect(icon.className).toContain("h-5");
|
expect(icon.className).toContain("h-5");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies large size", () => {
|
it("applies large size", () => {
|
||||||
render(
|
render(<Icon icon="check" size="lg" data-testid="icon" />);
|
||||||
createElement(Icon, {
|
|
||||||
icon: "check",
|
|
||||||
size: "lg",
|
|
||||||
"data-testid": "icon",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon.className).toContain("w-6");
|
expect(icon.className).toContain("w-6");
|
||||||
expect(icon.className).toContain("h-6");
|
expect(icon.className).toContain("h-6");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses md size by default", () => {
|
it("uses md size by default", () => {
|
||||||
render(createElement(Icon, { icon: "check", "data-testid": "icon" }));
|
render(<Icon icon="check" data-testid="icon" />);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon.className).toContain("w-5");
|
expect(icon.className).toContain("w-5");
|
||||||
expect(icon.className).toContain("h-5");
|
expect(icon.className).toContain("h-5");
|
||||||
|
|
@ -88,7 +69,7 @@ describe("Icon", () => {
|
||||||
|
|
||||||
describe("Icon Variants", () => {
|
describe("Icon Variants", () => {
|
||||||
it("renders copy icon", () => {
|
it("renders copy icon", () => {
|
||||||
render(createElement(Icon, { icon: "copy", "data-testid": "icon" }));
|
render(<Icon icon="copy" data-testid="icon" />);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
const path = icon.querySelector("path");
|
const path = icon.querySelector("path");
|
||||||
expect(path).toHaveAttribute(
|
expect(path).toHaveAttribute(
|
||||||
|
|
@ -98,9 +79,7 @@ describe("Icon", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders externalLink icon", () => {
|
it("renders externalLink icon", () => {
|
||||||
render(
|
render(<Icon icon="externalLink" data-testid="icon" />);
|
||||||
createElement(Icon, { icon: "externalLink", "data-testid": "icon" }),
|
|
||||||
);
|
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
const path = icon.querySelector("path");
|
const path = icon.querySelector("path");
|
||||||
expect(path).toHaveAttribute(
|
expect(path).toHaveAttribute(
|
||||||
|
|
@ -110,14 +89,14 @@ describe("Icon", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders github icon", () => {
|
it("renders github icon", () => {
|
||||||
render(createElement(Icon, { icon: "github", "data-testid": "icon" }));
|
render(<Icon icon="github" data-testid="icon" />);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
const path = icon.querySelector("path");
|
const path = icon.querySelector("path");
|
||||||
expect(path?.getAttribute("d")).toContain("M12 2C6.477 2 2 6.477 2 12");
|
expect(path?.getAttribute("d")).toContain("M12 2C6.477 2 2 6.477 2 12");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders check icon", () => {
|
it("renders check icon", () => {
|
||||||
render(createElement(Icon, { icon: "check", "data-testid": "icon" }));
|
render(<Icon icon="check" data-testid="icon" />);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
const path = icon.querySelector("path");
|
const path = icon.querySelector("path");
|
||||||
expect(path).toHaveAttribute(
|
expect(path).toHaveAttribute(
|
||||||
|
|
@ -127,7 +106,7 @@ describe("Icon", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders x icon", () => {
|
it("renders x icon", () => {
|
||||||
render(createElement(Icon, { icon: "x", "data-testid": "icon" }));
|
render(<Icon icon="x" data-testid="icon" />);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
const path = icon.querySelector("path");
|
const path = icon.querySelector("path");
|
||||||
expect(path).toHaveAttribute(
|
expect(path).toHaveAttribute(
|
||||||
|
|
@ -137,9 +116,7 @@ describe("Icon", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders chevronDown icon", () => {
|
it("renders chevronDown icon", () => {
|
||||||
render(
|
render(<Icon icon="chevronDown" data-testid="icon" />);
|
||||||
createElement(Icon, { icon: "chevronDown", "data-testid": "icon" }),
|
|
||||||
);
|
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
const path = icon.querySelector("path");
|
const path = icon.querySelector("path");
|
||||||
expect(path).toHaveAttribute(
|
expect(path).toHaveAttribute(
|
||||||
|
|
@ -151,25 +128,13 @@ describe("Icon", () => {
|
||||||
|
|
||||||
describe("SVG Attributes", () => {
|
describe("SVG Attributes", () => {
|
||||||
it("passes through className", () => {
|
it("passes through className", () => {
|
||||||
render(
|
render(<Icon icon="check" className="custom-class" data-testid="icon" />);
|
||||||
createElement(Icon, {
|
|
||||||
icon: "check",
|
|
||||||
className: "custom-class",
|
|
||||||
"data-testid": "icon",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon.className).toContain("custom-class");
|
expect(icon.className).toContain("custom-class");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("merges custom className with variant classes", () => {
|
it("merges custom className with variant classes", () => {
|
||||||
render(
|
render(<Icon icon="check" className="text-red-500" data-testid="icon" />);
|
||||||
createElement(Icon, {
|
|
||||||
icon: "check",
|
|
||||||
className: "text-red-500",
|
|
||||||
"data-testid": "icon",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon.className).toContain("text-red-500");
|
expect(icon.className).toContain("text-red-500");
|
||||||
expect(icon.className).toContain("inline-block");
|
expect(icon.className).toContain("inline-block");
|
||||||
|
|
@ -177,38 +142,26 @@ describe("Icon", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows custom aria-hidden", () => {
|
it("allows custom aria-hidden", () => {
|
||||||
render(
|
render(<Icon icon="check" aria-hidden={false} data-testid="icon" />);
|
||||||
createElement(Icon, {
|
|
||||||
icon: "check",
|
|
||||||
"aria-hidden": false,
|
|
||||||
"data-testid": "icon",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon).toHaveAttribute("aria-hidden", "false");
|
expect(icon).toHaveAttribute("aria-hidden", "false");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes through aria-label", () => {
|
it("passes through aria-label", () => {
|
||||||
render(
|
render(
|
||||||
createElement(Icon, {
|
<Icon
|
||||||
icon: "check",
|
icon="check"
|
||||||
"aria-label": "Success",
|
aria-label="Success"
|
||||||
"aria-hidden": false,
|
aria-hidden={false}
|
||||||
"data-testid": "icon",
|
data-testid="icon"
|
||||||
}),
|
/>,
|
||||||
);
|
);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon).toHaveAttribute("aria-label", "Success");
|
expect(icon).toHaveAttribute("aria-label", "Success");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes through id attribute", () => {
|
it("passes through id attribute", () => {
|
||||||
render(
|
render(<Icon icon="check" id="icon-id" data-testid="icon" />);
|
||||||
createElement(Icon, {
|
|
||||||
icon: "check",
|
|
||||||
id: "icon-id",
|
|
||||||
"data-testid": "icon",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon).toHaveAttribute("id", "icon-id");
|
expect(icon).toHaveAttribute("id", "icon-id");
|
||||||
});
|
});
|
||||||
|
|
@ -218,12 +171,12 @@ describe("Icon", () => {
|
||||||
it("forwards ref to SVG element", () => {
|
it("forwards ref to SVG element", () => {
|
||||||
let ref: SVGSVGElement | null = null;
|
let ref: SVGSVGElement | null = null;
|
||||||
render(
|
render(
|
||||||
createElement(Icon, {
|
<Icon
|
||||||
icon: "check",
|
icon="check"
|
||||||
ref: (el: SVGSVGElement | null) => {
|
ref={(el: SVGSVGElement | null) => {
|
||||||
ref = el;
|
ref = el;
|
||||||
},
|
}}
|
||||||
}),
|
/>,
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(SVGSVGElement);
|
expect(ref).toBeInstanceOf(SVGSVGElement);
|
||||||
expect(ref?.tagName).toBe("svg");
|
expect(ref?.tagName).toBe("svg");
|
||||||
|
|
@ -232,7 +185,7 @@ describe("Icon", () => {
|
||||||
|
|
||||||
describe("Base Classes", () => {
|
describe("Base Classes", () => {
|
||||||
it("always includes base classes", () => {
|
it("always includes base classes", () => {
|
||||||
render(createElement(Icon, { icon: "check", "data-testid": "icon" }));
|
render(<Icon icon="check" data-testid="icon" />);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon.className).toContain("inline-block");
|
expect(icon.className).toContain("inline-block");
|
||||||
expect(icon.className).toContain("fill-current");
|
expect(icon.className).toContain("fill-current");
|
||||||
|
|
@ -242,12 +195,12 @@ describe("Icon", () => {
|
||||||
describe("Compound Scenarios", () => {
|
describe("Compound Scenarios", () => {
|
||||||
it("works correctly with custom size and className", () => {
|
it("works correctly with custom size and className", () => {
|
||||||
render(
|
render(
|
||||||
createElement(Icon, {
|
<Icon
|
||||||
icon: "github",
|
icon="github"
|
||||||
size: "lg",
|
size="lg"
|
||||||
className: "text-zinc-400 hover:text-zinc-100",
|
className="text-zinc-400 hover:text-zinc-100"
|
||||||
"data-testid": "icon",
|
data-testid="icon"
|
||||||
}),
|
/>,
|
||||||
);
|
);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon.className).toContain("w-6");
|
expect(icon.className).toContain("w-6");
|
||||||
|
|
@ -258,13 +211,13 @@ describe("Icon", () => {
|
||||||
|
|
||||||
it("works correctly with aria attributes and custom size", () => {
|
it("works correctly with aria attributes and custom size", () => {
|
||||||
render(
|
render(
|
||||||
createElement(Icon, {
|
<Icon
|
||||||
icon: "externalLink",
|
icon="externalLink"
|
||||||
size: "sm",
|
size="sm"
|
||||||
"aria-label": "Opens in new tab",
|
aria-label="Opens in new tab"
|
||||||
"aria-hidden": false,
|
aria-hidden={false}
|
||||||
"data-testid": "icon",
|
data-testid="icon"
|
||||||
}),
|
/>,
|
||||||
);
|
);
|
||||||
const icon = screen.getByTestId("icon");
|
const icon = screen.getByTestId("icon");
|
||||||
expect(icon.className).toContain("w-4");
|
expect(icon.className).toContain("w-4");
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { cn } from "@tpmjs/utils/cn";
|
import { cn } from "@tpmjs/utils/cn";
|
||||||
import { createElement, forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
import { type IconName, icons } from "./icons";
|
import { type IconName, icons } from "./icons";
|
||||||
import type { IconProps } from "./types";
|
import type { IconProps } from "./types";
|
||||||
import { iconVariants } from "./variants";
|
import { iconVariants } from "./variants";
|
||||||
|
|
@ -11,19 +11,13 @@ export type { IconName };
|
||||||
*
|
*
|
||||||
* Renders SVG icons with consistent sizing and styling.
|
* Renders SVG icons with consistent sizing and styling.
|
||||||
* Icons inherit text color via currentColor.
|
* Icons inherit text color via currentColor.
|
||||||
* Built with .ts-only React using createElement.
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```tsx
|
||||||
* import { Icon } from '@tpmjs/ui/Icon/Icon';
|
* import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||||
* import { createElement } from 'react';
|
|
||||||
*
|
*
|
||||||
* function MyComponent() {
|
* function MyComponent() {
|
||||||
* return createElement(Icon, {
|
* return <Icon icon="github" size="md" className="text-zinc-400" />;
|
||||||
* icon: 'github',
|
|
||||||
* size: 'md',
|
|
||||||
* className: 'text-zinc-400',
|
|
||||||
* });
|
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
@ -31,24 +25,22 @@ export const Icon = forwardRef<SVGSVGElement, IconProps>(
|
||||||
({ className, icon, size = "md", ...props }, ref) => {
|
({ className, icon, size = "md", ...props }, ref) => {
|
||||||
const iconData = icons[icon];
|
const iconData = icons[icon];
|
||||||
|
|
||||||
return createElement(
|
return (
|
||||||
"svg",
|
<svg
|
||||||
{
|
ref={ref}
|
||||||
className: cn(
|
className={cn(
|
||||||
iconVariants({
|
iconVariants({
|
||||||
size,
|
size,
|
||||||
}),
|
}),
|
||||||
className,
|
className,
|
||||||
),
|
)}
|
||||||
viewBox: iconData.viewBox,
|
viewBox={iconData.viewBox}
|
||||||
fill: "currentColor",
|
fill="currentColor"
|
||||||
"aria-hidden": props["aria-hidden"] ?? true,
|
aria-hidden={props["aria-hidden"] ?? true}
|
||||||
ref,
|
{...props}
|
||||||
...props,
|
>
|
||||||
},
|
<path d={iconData.path} />
|
||||||
createElement("path", {
|
</svg>
|
||||||
d: iconData.path,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue