style: apply Biome formatting and auto-fixes
- Format 78 files, fixed 42 files total - Apply safe lint fixes (non-null assertions converted to optional chaining) - Remaining lint warnings are non-blocking (test code, a11y suggestions) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
65be671143
commit
e904e8d319
34 changed files with 167 additions and 525 deletions
|
|
@ -1,28 +1,31 @@
|
||||||
import reactConfig from '@tpmjs/eslint-config/react.js';
|
import reactConfig from "@tpmjs/eslint-config/react.js";
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
ignores: ['dist/**', 'node_modules/**', 'eslint.config.mjs']
|
ignores: ["dist/**", "node_modules/**", "eslint.config.mjs"],
|
||||||
},
|
},
|
||||||
...reactConfig,
|
...reactConfig,
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
'import/no-internal-modules': ['error', {
|
"import/no-internal-modules": [
|
||||||
allow: [
|
"error",
|
||||||
'@tpmjs/ui/*/[!index]*',
|
{
|
||||||
'@tpmjs/utils/*',
|
allow: [
|
||||||
'@tpmjs/types/*',
|
"@tpmjs/ui/*/[!index]*",
|
||||||
'@tpmjs/eslint-config/*',
|
"@tpmjs/utils/*",
|
||||||
'@testing-library/**',
|
"@tpmjs/types/*",
|
||||||
'react/*',
|
"@tpmjs/eslint-config/*",
|
||||||
]
|
"@testing-library/**",
|
||||||
}],
|
"react/*",
|
||||||
}
|
],
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
|
},
|
||||||
rules: {
|
},
|
||||||
'jsx-a11y/no-autofocus': 'off',
|
{
|
||||||
}
|
files: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"],
|
||||||
}
|
rules: {
|
||||||
|
"jsx-a11y/no-autofocus": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -250,7 +250,7 @@ describe("Badge", () => {
|
||||||
</Badge>,
|
</Badge>,
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(HTMLDivElement);
|
expect(ref).toBeInstanceOf(HTMLDivElement);
|
||||||
expect(ref!.tagName).toBe("DIV");
|
expect(ref?.tagName).toBe("DIV");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -297,11 +297,7 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("works correctly with outline variant and custom className", () => {
|
it("works correctly with outline variant and custom className", () => {
|
||||||
render(
|
render(
|
||||||
<Badge
|
<Badge variant="outline" className="hover:bg-accent" data-testid="badge">
|
||||||
variant="outline"
|
|
||||||
className="hover:bg-accent"
|
|
||||||
data-testid="badge"
|
|
||||||
>
|
|
||||||
Outlined
|
Outlined
|
||||||
</Badge>,
|
</Badge>,
|
||||||
);
|
);
|
||||||
|
|
@ -355,12 +351,7 @@ 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(
|
||||||
<Badge
|
<Badge variant="success" role="status" aria-label="Online status" data-testid="badge">
|
||||||
variant="success"
|
|
||||||
role="status"
|
|
||||||
aria-label="Online status"
|
|
||||||
data-testid="badge"
|
|
||||||
>
|
|
||||||
Online
|
Online
|
||||||
</Badge>,
|
</Badge>,
|
||||||
);
|
);
|
||||||
|
|
@ -372,12 +363,7 @@ describe("Badge", () => {
|
||||||
|
|
||||||
it("can be used as notification count", () => {
|
it("can be used as notification count", () => {
|
||||||
render(
|
render(
|
||||||
<Badge
|
<Badge variant="error" size="sm" aria-label="3 unread messages" data-testid="badge">
|
||||||
variant="error"
|
|
||||||
size="sm"
|
|
||||||
aria-label="3 unread messages"
|
|
||||||
data-testid="badge"
|
|
||||||
>
|
|
||||||
3
|
3
|
||||||
</Badge>,
|
</Badge>,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,7 @@ export interface BadgeProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
* Visual variant of the badge
|
* Visual variant of the badge
|
||||||
* @default 'default'
|
* @default 'default'
|
||||||
*/
|
*/
|
||||||
variant?:
|
variant?: "default" | "secondary" | "outline" | "success" | "error" | "warning" | "info";
|
||||||
| "default"
|
|
||||||
| "secondary"
|
|
||||||
| "outline"
|
|
||||||
| "success"
|
|
||||||
| "error"
|
|
||||||
| "warning"
|
|
||||||
| "info";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Size of the badge
|
* Size of the badge
|
||||||
|
|
|
||||||
|
|
@ -19,33 +19,17 @@ export const badgeVariants = createVariants({
|
||||||
|
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: [
|
default: ["bg-primary text-primary-foreground", "border border-primary"].join(" "),
|
||||||
"bg-primary text-primary-foreground",
|
|
||||||
"border border-primary",
|
|
||||||
].join(" "),
|
|
||||||
|
|
||||||
secondary: [
|
secondary: ["bg-secondary text-secondary-foreground", "border border-secondary"].join(" "),
|
||||||
"bg-secondary text-secondary-foreground",
|
|
||||||
"border border-secondary",
|
|
||||||
].join(" "),
|
|
||||||
|
|
||||||
outline: ["bg-transparent text-foreground", "border border-border"].join(
|
outline: ["bg-transparent text-foreground", "border border-border"].join(" "),
|
||||||
" ",
|
|
||||||
),
|
|
||||||
|
|
||||||
success: [
|
success: ["bg-success text-success-foreground", "border border-success"].join(" "),
|
||||||
"bg-success text-success-foreground",
|
|
||||||
"border border-success",
|
|
||||||
].join(" "),
|
|
||||||
|
|
||||||
error: ["bg-error text-error-foreground", "border border-error"].join(
|
error: ["bg-error text-error-foreground", "border border-error"].join(" "),
|
||||||
" ",
|
|
||||||
),
|
|
||||||
|
|
||||||
warning: [
|
warning: ["bg-warning text-warning-foreground", "border border-warning"].join(" "),
|
||||||
"bg-warning text-warning-foreground",
|
|
||||||
"border border-warning",
|
|
||||||
].join(" "),
|
|
||||||
|
|
||||||
info: ["bg-info text-info-foreground", "border border-info"].join(" "),
|
info: ["bg-info text-info-foreground", "border border-info"].join(" "),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -119,9 +119,7 @@ describe("Button", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies icon size classes", () => {
|
it("applies icon size classes", () => {
|
||||||
render(
|
render(<Button size="icon" data-testid="button" aria-label="Icon button" />);
|
||||||
<Button size="icon" data-testid="button" aria-label="Icon button" />,
|
|
||||||
);
|
|
||||||
const button = screen.getByTestId("button");
|
const button = screen.getByTestId("button");
|
||||||
expect(button.className).toContain("h-10");
|
expect(button.className).toContain("h-10");
|
||||||
expect(button.className).toContain("w-10");
|
expect(button.className).toContain("w-10");
|
||||||
|
|
@ -241,19 +239,14 @@ describe("Button", () => {
|
||||||
</Button>,
|
</Button>,
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(HTMLButtonElement);
|
expect(ref).toBeInstanceOf(HTMLButtonElement);
|
||||||
expect(ref!.tagName).toBe("BUTTON");
|
expect(ref?.tagName).toBe("BUTTON");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("HTML Attributes", () => {
|
describe("HTML Attributes", () => {
|
||||||
it("passes through HTML button attributes", () => {
|
it("passes through HTML button attributes", () => {
|
||||||
render(
|
render(
|
||||||
<Button
|
<Button type="submit" name="test-button" value="test-value" data-testid="button">
|
||||||
type="submit"
|
|
||||||
name="test-button"
|
|
||||||
value="test-value"
|
|
||||||
data-testid="button"
|
|
||||||
>
|
|
||||||
Submit
|
Submit
|
||||||
</Button>,
|
</Button>,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -65,11 +65,9 @@ export const buttonVariants = createVariants({
|
||||||
"active:bg-accent/80",
|
"active:bg-accent/80",
|
||||||
].join(" "),
|
].join(" "),
|
||||||
|
|
||||||
link: [
|
link: ["text-primary underline-offset-4", "hover:underline", "active:text-primary/80"].join(
|
||||||
"text-primary underline-offset-4",
|
" ",
|
||||||
"hover:underline",
|
),
|
||||||
"active:text-primary/80",
|
|
||||||
].join(" "),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
size: {
|
size: {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./Card";
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "./Card";
|
|
||||||
|
|
||||||
describe("Card", () => {
|
describe("Card", () => {
|
||||||
describe("Rendering", () => {
|
describe("Rendering", () => {
|
||||||
|
|
@ -23,9 +16,7 @@ describe("Card", () => {
|
||||||
<Card data-testid="card">
|
<Card data-testid="card">
|
||||||
<CardHeader data-testid="header">
|
<CardHeader data-testid="header">
|
||||||
<CardTitle data-testid="title">Title</CardTitle>
|
<CardTitle data-testid="title">Title</CardTitle>
|
||||||
<CardDescription data-testid="description">
|
<CardDescription data-testid="description">Description</CardDescription>
|
||||||
Description
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent data-testid="content">Content</CardContent>
|
<CardContent data-testid="content">Content</CardContent>
|
||||||
<CardFooter data-testid="footer">Footer</CardFooter>
|
<CardFooter data-testid="footer">Footer</CardFooter>
|
||||||
|
|
@ -236,9 +227,7 @@ describe("Card", () => {
|
||||||
render(
|
render(
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardDescription data-testid="description">
|
<CardDescription data-testid="description">Description text</CardDescription>
|
||||||
Description text
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>,
|
</Card>,
|
||||||
);
|
);
|
||||||
|
|
@ -250,9 +239,7 @@ describe("Card", () => {
|
||||||
render(
|
render(
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardDescription data-testid="description">
|
<CardDescription data-testid="description">Description</CardDescription>
|
||||||
Description
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>,
|
</Card>,
|
||||||
);
|
);
|
||||||
|
|
@ -417,7 +404,7 @@ describe("Card", () => {
|
||||||
</Card>,
|
</Card>,
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(HTMLDivElement);
|
expect(ref).toBeInstanceOf(HTMLDivElement);
|
||||||
expect(ref!.tagName).toBe("DIV");
|
expect(ref?.tagName).toBe("DIV");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("forwards ref to CardHeader element", () => {
|
it("forwards ref to CardHeader element", () => {
|
||||||
|
|
@ -452,7 +439,7 @@ describe("Card", () => {
|
||||||
</Card>,
|
</Card>,
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(HTMLHeadingElement);
|
expect(ref).toBeInstanceOf(HTMLHeadingElement);
|
||||||
expect(ref!.tagName).toBe("H3");
|
expect(ref?.tagName).toBe("H3");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("forwards ref to CardDescription element", () => {
|
it("forwards ref to CardDescription element", () => {
|
||||||
|
|
@ -574,9 +561,7 @@ describe("Card", () => {
|
||||||
render(
|
render(
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardDescription data-testid="description">
|
<CardDescription data-testid="description">Description</CardDescription>
|
||||||
Description
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>,
|
</Card>,
|
||||||
);
|
);
|
||||||
|
|
@ -606,9 +591,7 @@ describe("Card", () => {
|
||||||
<CardTitle>Card Title</CardTitle>
|
<CardTitle>Card Title</CardTitle>
|
||||||
<CardDescription>This is a card description</CardDescription>
|
<CardDescription>This is a card description</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent data-testid="content">
|
<CardContent data-testid="content">Card content goes here</CardContent>
|
||||||
Card content goes here
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter data-testid="footer">Footer actions</CardFooter>
|
<CardFooter data-testid="footer">Footer actions</CardFooter>
|
||||||
</Card>,
|
</Card>,
|
||||||
);
|
);
|
||||||
|
|
@ -616,9 +599,7 @@ describe("Card", () => {
|
||||||
const card = screen.getByTestId("card");
|
const card = screen.getByTestId("card");
|
||||||
expect(card).toBeInTheDocument();
|
expect(card).toBeInTheDocument();
|
||||||
expect(screen.getByText("Card Title")).toBeInTheDocument();
|
expect(screen.getByText("Card Title")).toBeInTheDocument();
|
||||||
expect(
|
expect(screen.getByText("This is a card description")).toBeInTheDocument();
|
||||||
screen.getByText("This is a card description"),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(screen.getByText("Card content goes here")).toBeInTheDocument();
|
expect(screen.getByText("Card content goes here")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Footer actions")).toBeInTheDocument();
|
expect(screen.getByText("Footer actions")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -93,13 +93,7 @@ CardHeader.displayName = "CardHeader";
|
||||||
export const CardTitle = forwardRef<HTMLHeadingElement, CardTitleProps>(
|
export const CardTitle = forwardRef<HTMLHeadingElement, CardTitleProps>(
|
||||||
({ className, as = "h3", ...props }, ref) => {
|
({ className, as = "h3", ...props }, ref) => {
|
||||||
const Component = as;
|
const Component = as;
|
||||||
return (
|
return <Component ref={ref} className={cn(cardTitleVariants(), className)} {...props} />;
|
||||||
<Component
|
|
||||||
ref={ref}
|
|
||||||
className={cn(cardTitleVariants(), className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -110,18 +104,11 @@ CardTitle.displayName = "CardTitle";
|
||||||
*
|
*
|
||||||
* Description text for a card, typically placed below the title.
|
* Description text for a card, typically placed below the title.
|
||||||
*/
|
*/
|
||||||
export const CardDescription = forwardRef<
|
export const CardDescription = forwardRef<HTMLParagraphElement, CardDescriptionProps>(
|
||||||
HTMLParagraphElement,
|
({ className, ...props }, ref) => {
|
||||||
CardDescriptionProps
|
return <p ref={ref} className={cn(cardDescriptionVariants(), className)} {...props} />;
|
||||||
>(({ className, ...props }, ref) => {
|
},
|
||||||
return (
|
);
|
||||||
<p
|
|
||||||
ref={ref}
|
|
||||||
className={cn(cardDescriptionVariants(), className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
CardDescription.displayName = "CardDescription";
|
CardDescription.displayName = "CardDescription";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,7 @@ export const cardVariants = createVariants({
|
||||||
"shadow-md",
|
"shadow-md",
|
||||||
].join(" "),
|
].join(" "),
|
||||||
|
|
||||||
outline: [
|
outline: ["border-2 border-dotted border-border", "bg-transparent text-foreground"].join(" "),
|
||||||
"border-2 border-dotted border-border",
|
|
||||||
"bg-transparent text-foreground",
|
|
||||||
].join(" "),
|
|
||||||
|
|
||||||
blueprint: [
|
blueprint: [
|
||||||
"border border-dotted border-border",
|
"border border-dotted border-border",
|
||||||
|
|
@ -85,10 +82,7 @@ export const cardHeaderVariants = createVariants({
|
||||||
* CardTitle variant definitions
|
* CardTitle variant definitions
|
||||||
*/
|
*/
|
||||||
export const cardTitleVariants = createVariants({
|
export const cardTitleVariants = createVariants({
|
||||||
base: [
|
base: ["text-2xl font-semibold leading-none tracking-tight", "text-foreground"].join(" "),
|
||||||
"text-2xl font-semibold leading-none tracking-tight",
|
|
||||||
"text-foreground",
|
|
||||||
].join(" "),
|
|
||||||
|
|
||||||
variants: {},
|
variants: {},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,7 @@ describe("CodeBlock", () => {
|
||||||
|
|
||||||
describe("Language", () => {
|
describe("Language", () => {
|
||||||
it("sets data-language attribute", () => {
|
it("sets data-language attribute", () => {
|
||||||
render(
|
render(<CodeBlock code="const x = 5" language="javascript" data-testid="codeblock" />);
|
||||||
<CodeBlock
|
|
||||||
code="const x = 5"
|
|
||||||
language="javascript"
|
|
||||||
data-testid="codeblock"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const codeblock = screen.getByTestId("codeblock");
|
const codeblock = screen.getByTestId("codeblock");
|
||||||
const code = codeblock.querySelector("code");
|
const code = codeblock.querySelector("code");
|
||||||
expect(code).toHaveAttribute("data-language", "javascript");
|
expect(code).toHaveAttribute("data-language", "javascript");
|
||||||
|
|
@ -77,9 +71,7 @@ describe("CodeBlock", () => {
|
||||||
let code = codeblock.querySelector("code");
|
let code = codeblock.querySelector("code");
|
||||||
expect(code).toHaveAttribute("data-language", "python");
|
expect(code).toHaveAttribute("data-language", "python");
|
||||||
|
|
||||||
rerender(
|
rerender(<CodeBlock code="code" language="bash" data-testid="codeblock" />);
|
||||||
<CodeBlock code="code" language="bash" data-testid="codeblock" />,
|
|
||||||
);
|
|
||||||
codeblock = screen.getByTestId("codeblock");
|
codeblock = screen.getByTestId("codeblock");
|
||||||
code = codeblock.querySelector("code");
|
code = codeblock.querySelector("code");
|
||||||
expect(code).toHaveAttribute("data-language", "bash");
|
expect(code).toHaveAttribute("data-language", "bash");
|
||||||
|
|
@ -223,12 +215,8 @@ describe("CodeBlock", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles clipboard API errors gracefully", async () => {
|
it("handles clipboard API errors gracefully", async () => {
|
||||||
const consoleErrorSpy = vi
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||||
.spyOn(console, "error")
|
mockClipboard.writeText.mockRejectedValueOnce(new Error("Clipboard not available"));
|
||||||
.mockImplementation(() => {});
|
|
||||||
mockClipboard.writeText.mockRejectedValueOnce(
|
|
||||||
new Error("Clipboard not available"),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
render(<CodeBlock code="test code" />);
|
render(<CodeBlock code="test code" />);
|
||||||
|
|
@ -249,17 +237,13 @@ describe("CodeBlock", () => {
|
||||||
|
|
||||||
describe("HTML Attributes", () => {
|
describe("HTML Attributes", () => {
|
||||||
it("passes through id attribute", () => {
|
it("passes through id attribute", () => {
|
||||||
render(
|
render(<CodeBlock code="code" id="codeblock-id" data-testid="codeblock" />);
|
||||||
<CodeBlock code="code" id="codeblock-id" data-testid="codeblock" />,
|
|
||||||
);
|
|
||||||
const codeblock = screen.getByTestId("codeblock");
|
const codeblock = screen.getByTestId("codeblock");
|
||||||
expect(codeblock).toHaveAttribute("id", "codeblock-id");
|
expect(codeblock).toHaveAttribute("id", "codeblock-id");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes through data attributes", () => {
|
it("passes through data attributes", () => {
|
||||||
render(
|
render(<CodeBlock code="code" data-custom="test" data-testid="codeblock" />);
|
||||||
<CodeBlock code="code" data-custom="test" data-testid="codeblock" />,
|
|
||||||
);
|
|
||||||
const codeblock = screen.getByTestId("codeblock");
|
const codeblock = screen.getByTestId("codeblock");
|
||||||
expect(codeblock).toHaveAttribute("data-custom", "test");
|
expect(codeblock).toHaveAttribute("data-custom", "test");
|
||||||
});
|
});
|
||||||
|
|
@ -267,13 +251,7 @@ describe("CodeBlock", () => {
|
||||||
|
|
||||||
describe("Custom className", () => {
|
describe("Custom className", () => {
|
||||||
it("merges custom className with variant classes", () => {
|
it("merges custom className with variant classes", () => {
|
||||||
render(
|
render(<CodeBlock code="code" className="custom-class" data-testid="codeblock" />);
|
||||||
<CodeBlock
|
|
||||||
code="code"
|
|
||||||
className="custom-class"
|
|
||||||
data-testid="codeblock"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const codeblock = screen.getByTestId("codeblock");
|
const codeblock = screen.getByTestId("codeblock");
|
||||||
expect(codeblock.className).toContain("custom-class");
|
expect(codeblock.className).toContain("custom-class");
|
||||||
expect(codeblock.className).toContain("bg-background");
|
expect(codeblock.className).toContain("bg-background");
|
||||||
|
|
@ -293,7 +271,7 @@ describe("CodeBlock", () => {
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(HTMLDivElement);
|
expect(ref).toBeInstanceOf(HTMLDivElement);
|
||||||
expect(ref!.querySelector("code")).toBeInTheDocument();
|
expect(ref?.querySelector("code")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,17 +32,7 @@ import {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||||
(
|
({ className, code, language = "text", size = "md", showCopy = true, ...props }, ref) => {
|
||||||
{
|
|
||||||
className,
|
|
||||||
code,
|
|
||||||
language = "text",
|
|
||||||
size = "md",
|
|
||||||
showCopy = true,
|
|
||||||
...props
|
|
||||||
},
|
|
||||||
ref,
|
|
||||||
) => {
|
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
const handleCopy = async () => {
|
const handleCopy = async () => {
|
||||||
|
|
@ -57,11 +47,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div ref={ref} className={cn(codeBlockContainerVariants(), className)} {...props}>
|
||||||
ref={ref}
|
|
||||||
className={cn(codeBlockContainerVariants(), className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<code
|
<code
|
||||||
className={codeBlockCodeVariants({
|
className={codeBlockCodeVariants({
|
||||||
size,
|
size,
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ import type { HTMLAttributes } from "react";
|
||||||
/**
|
/**
|
||||||
* CodeBlock component props
|
* CodeBlock component props
|
||||||
*/
|
*/
|
||||||
export interface CodeBlockProps
|
export interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
||||||
extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
||||||
/**
|
/**
|
||||||
* Code content to display
|
* Code content to display
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,7 @@ describe("Container", () => {
|
||||||
</Container>,
|
</Container>,
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(HTMLDivElement);
|
expect(ref).toBeInstanceOf(HTMLDivElement);
|
||||||
expect(ref!.tagName).toBe("DIV");
|
expect(ref?.tagName).toBe("DIV");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -281,13 +281,7 @@ describe("GridContainer", () => {
|
||||||
describe("Compound Scenarios", () => {
|
describe("Compound Scenarios", () => {
|
||||||
it("works with multiple variants combined", () => {
|
it("works with multiple variants combined", () => {
|
||||||
render(
|
render(
|
||||||
<GridContainer
|
<GridContainer columns={4} gap="lg" align="center" justify="center" data-testid="grid">
|
||||||
columns={4}
|
|
||||||
gap="lg"
|
|
||||||
align="center"
|
|
||||||
justify="center"
|
|
||||||
data-testid="grid"
|
|
||||||
>
|
|
||||||
Content
|
Content
|
||||||
</GridContainer>,
|
</GridContainer>,
|
||||||
);
|
);
|
||||||
|
|
@ -300,12 +294,7 @@ describe("GridContainer", () => {
|
||||||
|
|
||||||
it("works with fixed columns and custom className", () => {
|
it("works with fixed columns and custom className", () => {
|
||||||
render(
|
render(
|
||||||
<GridContainer
|
<GridContainer columns={3} responsive="fixed" className="my-custom-grid" data-testid="grid">
|
||||||
columns={3}
|
|
||||||
responsive="fixed"
|
|
||||||
className="my-custom-grid"
|
|
||||||
data-testid="grid"
|
|
||||||
>
|
|
||||||
Content
|
Content
|
||||||
</GridContainer>,
|
</GridContainer>,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -77,11 +77,7 @@ describe("Header", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders ReactNode as actions", () => {
|
it("renders ReactNode as actions", () => {
|
||||||
render(
|
render(<Header actions={<div data-testid="custom-actions">Custom Actions</div>} />);
|
||||||
<Header
|
|
||||||
actions={<div data-testid="custom-actions">Custom Actions</div>}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId("custom-actions")).toBeInTheDocument();
|
expect(screen.getByTestId("custom-actions")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -255,7 +251,7 @@ describe("Header", () => {
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(HTMLElement);
|
expect(ref).toBeInstanceOf(HTMLElement);
|
||||||
expect(ref!.tagName).toBe("HEADER");
|
expect(ref?.tagName).toBe("HEADER");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -275,15 +271,7 @@ describe("Header", () => {
|
||||||
|
|
||||||
describe("Compound Scenarios", () => {
|
describe("Compound Scenarios", () => {
|
||||||
it("works correctly with large size and sticky", () => {
|
it("works correctly with large size and sticky", () => {
|
||||||
render(
|
render(<Header title="App" actions="Sign In" size="lg" sticky={true} data-testid="header" />);
|
||||||
<Header
|
|
||||||
title="App"
|
|
||||||
actions="Sign In"
|
|
||||||
size="lg"
|
|
||||||
sticky={true}
|
|
||||||
data-testid="header"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const header = screen.getByTestId("header");
|
const header = screen.getByTestId("header");
|
||||||
expect(header.className).toContain("h-20");
|
expect(header.className).toContain("h-20");
|
||||||
expect(header.className).toContain("px-8");
|
expect(header.className).toContain("px-8");
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
import { cn } from "@tpmjs/utils/cn";
|
import { cn } from "@tpmjs/utils/cn";
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
import type { HeaderProps } from "./types";
|
import type { HeaderProps } from "./types";
|
||||||
import {
|
import { headerActionsVariants, headerTitleVariants, headerVariants } from "./variants";
|
||||||
headerActionsVariants,
|
|
||||||
headerTitleVariants,
|
|
||||||
headerVariants,
|
|
||||||
} from "./variants";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Header component
|
* Header component
|
||||||
|
|
@ -31,18 +27,7 @@ import {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const Header = forwardRef<HTMLElement, HeaderProps>(
|
export const Header = forwardRef<HTMLElement, HeaderProps>(
|
||||||
(
|
({ className, title, actions, size = "md", sticky = false, children, ...props }, ref) => {
|
||||||
{
|
|
||||||
className,
|
|
||||||
title,
|
|
||||||
actions,
|
|
||||||
size = "md",
|
|
||||||
sticky = false,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
},
|
|
||||||
ref,
|
|
||||||
) => {
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -66,10 +51,7 @@ export const Header = forwardRef<HTMLElement, HeaderProps>(
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{children && (
|
{children && (
|
||||||
<div
|
<div className="flex-1 flex items-center justify-center" data-testid="header-children">
|
||||||
className="flex-1 flex items-center justify-center"
|
|
||||||
data-testid="header-children"
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ import type { HTMLAttributes, ReactNode } from "react";
|
||||||
/**
|
/**
|
||||||
* Header component props
|
* Header component props
|
||||||
*/
|
*/
|
||||||
export interface HeaderProps
|
export interface HeaderProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
||||||
extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
|
||||||
/**
|
/**
|
||||||
* Title/logo content for the left side
|
* Title/logo content for the left side
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -119,10 +119,7 @@ describe("Icon", () => {
|
||||||
render(<Icon icon="chevronDown" data-testid="icon" />);
|
render(<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("d", "M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z");
|
||||||
"d",
|
|
||||||
"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -148,14 +145,7 @@ describe("Icon", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes through aria-label", () => {
|
it("passes through aria-label", () => {
|
||||||
render(
|
render(<Icon icon="check" aria-label="Success" aria-hidden={false} data-testid="icon" />);
|
||||||
<Icon
|
|
||||||
icon="check"
|
|
||||||
aria-label="Success"
|
|
||||||
aria-hidden={false}
|
|
||||||
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");
|
||||||
});
|
});
|
||||||
|
|
@ -179,7 +169,7 @@ describe("Icon", () => {
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(SVGSVGElement);
|
expect(ref).toBeInstanceOf(SVGSVGElement);
|
||||||
expect(ref!.tagName).toBe("svg");
|
expect(ref?.tagName).toBe("svg");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ import type { IconName } from "./icons";
|
||||||
/**
|
/**
|
||||||
* Icon component props
|
* Icon component props
|
||||||
*/
|
*/
|
||||||
export interface IconProps
|
export interface IconProps extends Omit<SVGAttributes<SVGSVGElement>, "children"> {
|
||||||
extends Omit<SVGAttributes<SVGSVGElement>, "children"> {
|
|
||||||
/**
|
/**
|
||||||
* Icon to display
|
* Icon to display
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -196,9 +196,7 @@ describe("Input", () => {
|
||||||
|
|
||||||
describe("Value and onChange", () => {
|
describe("Value and onChange", () => {
|
||||||
it("renders with initial value", () => {
|
it("renders with initial value", () => {
|
||||||
render(
|
render(<Input value="test value" onChange={() => {}} data-testid="input" />);
|
||||||
<Input value="test value" onChange={() => {}} data-testid="input" />,
|
|
||||||
);
|
|
||||||
const input = screen.getByTestId("input") as HTMLInputElement;
|
const input = screen.getByTestId("input") as HTMLInputElement;
|
||||||
expect(input.value).toBe("test value");
|
expect(input.value).toBe("test value");
|
||||||
});
|
});
|
||||||
|
|
@ -317,7 +315,7 @@ describe("Input", () => {
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(HTMLInputElement);
|
expect(ref).toBeInstanceOf(HTMLInputElement);
|
||||||
expect(ref!.tagName).toBe("INPUT");
|
expect(ref?.tagName).toBe("INPUT");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can focus input through ref", () => {
|
it("can focus input through ref", () => {
|
||||||
|
|
@ -330,7 +328,7 @@ describe("Input", () => {
|
||||||
data-testid="input"
|
data-testid="input"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
ref!.focus();
|
ref?.focus();
|
||||||
expect(screen.getByTestId("input")).toHaveFocus();
|
expect(screen.getByTestId("input")).toHaveFocus();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ import type { InputHTMLAttributes } from "react";
|
||||||
/**
|
/**
|
||||||
* Input component props
|
* Input component props
|
||||||
*/
|
*/
|
||||||
export interface InputProps
|
export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size"> {
|
||||||
extends Omit<InputHTMLAttributes<HTMLInputElement>, "size"> {
|
|
||||||
/**
|
/**
|
||||||
* Visual state of the input
|
* Visual state of the input
|
||||||
* @default 'default'
|
* @default 'default'
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ describe("Label", () => {
|
||||||
</Label>,
|
</Label>,
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(HTMLLabelElement);
|
expect(ref).toBeInstanceOf(HTMLLabelElement);
|
||||||
expect(ref!.tagName).toBe("LABEL");
|
expect(ref?.tagName).toBe("LABEL");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,7 @@ import { labelVariants } from "./variants";
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const Label = forwardRef<HTMLLabelElement, LabelProps>(
|
export const Label = forwardRef<HTMLLabelElement, LabelProps>(
|
||||||
(
|
({ className, size = "md", required = false, disabled = false, children, ...props }, ref) => {
|
||||||
{
|
|
||||||
className,
|
|
||||||
size = "md",
|
|
||||||
required = false,
|
|
||||||
disabled = false,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
},
|
|
||||||
ref,
|
|
||||||
) => {
|
|
||||||
return (
|
return (
|
||||||
// biome-ignore lint/a11y/noLabelWithoutControl: This is a generic label component that can be used with htmlFor or wrap inputs
|
// biome-ignore lint/a11y/noLabelWithoutControl: This is a generic label component that can be used with htmlFor or wrap inputs
|
||||||
<label
|
<label
|
||||||
|
|
|
||||||
|
|
@ -103,36 +103,28 @@ describe("ProgressBar", () => {
|
||||||
|
|
||||||
describe("Color Variants", () => {
|
describe("Color Variants", () => {
|
||||||
it("applies primary variant", () => {
|
it("applies primary variant", () => {
|
||||||
render(
|
render(<ProgressBar value={50} variant="primary" data-testid="progress" />);
|
||||||
<ProgressBar value={50} variant="primary" data-testid="progress" />,
|
|
||||||
);
|
|
||||||
const progress = screen.getByTestId("progress");
|
const progress = screen.getByTestId("progress");
|
||||||
const fill = progress.querySelector("div");
|
const fill = progress.querySelector("div");
|
||||||
expect(fill?.className).toContain("bg-primary");
|
expect(fill?.className).toContain("bg-primary");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies success variant", () => {
|
it("applies success variant", () => {
|
||||||
render(
|
render(<ProgressBar value={50} variant="success" data-testid="progress" />);
|
||||||
<ProgressBar value={50} variant="success" data-testid="progress" />,
|
|
||||||
);
|
|
||||||
const progress = screen.getByTestId("progress");
|
const progress = screen.getByTestId("progress");
|
||||||
const fill = progress.querySelector("div");
|
const fill = progress.querySelector("div");
|
||||||
expect(fill?.className).toContain("bg-success");
|
expect(fill?.className).toContain("bg-success");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies warning variant", () => {
|
it("applies warning variant", () => {
|
||||||
render(
|
render(<ProgressBar value={50} variant="warning" data-testid="progress" />);
|
||||||
<ProgressBar value={50} variant="warning" data-testid="progress" />,
|
|
||||||
);
|
|
||||||
const progress = screen.getByTestId("progress");
|
const progress = screen.getByTestId("progress");
|
||||||
const fill = progress.querySelector("div");
|
const fill = progress.querySelector("div");
|
||||||
expect(fill?.className).toContain("bg-warning");
|
expect(fill?.className).toContain("bg-warning");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies danger variant", () => {
|
it("applies danger variant", () => {
|
||||||
render(
|
render(<ProgressBar value={50} variant="danger" data-testid="progress" />);
|
||||||
<ProgressBar value={50} variant="danger" data-testid="progress" />,
|
|
||||||
);
|
|
||||||
const progress = screen.getByTestId("progress");
|
const progress = screen.getByTestId("progress");
|
||||||
const fill = progress.querySelector("div");
|
const fill = progress.querySelector("div");
|
||||||
expect(fill?.className).toContain("bg-error");
|
expect(fill?.className).toContain("bg-error");
|
||||||
|
|
@ -183,17 +175,13 @@ describe("ProgressBar", () => {
|
||||||
|
|
||||||
describe("HTML Attributes", () => {
|
describe("HTML Attributes", () => {
|
||||||
it("passes through id attribute", () => {
|
it("passes through id attribute", () => {
|
||||||
render(
|
render(<ProgressBar value={50} id="progress-id" data-testid="progress" />);
|
||||||
<ProgressBar value={50} id="progress-id" data-testid="progress" />,
|
|
||||||
);
|
|
||||||
const progress = screen.getByTestId("progress");
|
const progress = screen.getByTestId("progress");
|
||||||
expect(progress).toHaveAttribute("id", "progress-id");
|
expect(progress).toHaveAttribute("id", "progress-id");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes through data attributes", () => {
|
it("passes through data attributes", () => {
|
||||||
render(
|
render(<ProgressBar value={50} data-custom="test" data-testid="progress" />);
|
||||||
<ProgressBar value={50} data-custom="test" data-testid="progress" />,
|
|
||||||
);
|
|
||||||
const progress = screen.getByTestId("progress");
|
const progress = screen.getByTestId("progress");
|
||||||
expect(progress).toHaveAttribute("data-custom", "test");
|
expect(progress).toHaveAttribute("data-custom", "test");
|
||||||
});
|
});
|
||||||
|
|
@ -201,13 +189,7 @@ describe("ProgressBar", () => {
|
||||||
|
|
||||||
describe("Custom className", () => {
|
describe("Custom className", () => {
|
||||||
it("merges custom className with variant classes", () => {
|
it("merges custom className with variant classes", () => {
|
||||||
render(
|
render(<ProgressBar value={50} className="custom-class" data-testid="progress" />);
|
||||||
<ProgressBar
|
|
||||||
value={50}
|
|
||||||
className="custom-class"
|
|
||||||
data-testid="progress"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const progress = screen.getByTestId("progress");
|
const progress = screen.getByTestId("progress");
|
||||||
expect(progress.className).toContain("custom-class");
|
expect(progress.className).toContain("custom-class");
|
||||||
expect(progress.className).toContain("bg-surface");
|
expect(progress.className).toContain("bg-surface");
|
||||||
|
|
@ -253,14 +235,7 @@ describe("ProgressBar", () => {
|
||||||
|
|
||||||
describe("Compound Scenarios", () => {
|
describe("Compound Scenarios", () => {
|
||||||
it("works correctly with large size and success variant", () => {
|
it("works correctly with large size and success variant", () => {
|
||||||
render(
|
render(<ProgressBar value={80} size="lg" variant="success" data-testid="progress" />);
|
||||||
<ProgressBar
|
|
||||||
value={80}
|
|
||||||
size="lg"
|
|
||||||
variant="success"
|
|
||||||
data-testid="progress"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const progress = screen.getByTestId("progress");
|
const progress = screen.getByTestId("progress");
|
||||||
expect(progress.className).toContain("h-3");
|
expect(progress.className).toContain("h-3");
|
||||||
const fill = progress.querySelector("div");
|
const fill = progress.querySelector("div");
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,7 @@ import { progressBarFillVariants, progressBarTrackVariants } from "./variants";
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
|
export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
|
||||||
(
|
({ className, value, size = "md", variant = "primary", showLabel = false, ...props }, ref) => {
|
||||||
{
|
|
||||||
className,
|
|
||||||
value,
|
|
||||||
size = "md",
|
|
||||||
variant = "primary",
|
|
||||||
showLabel = false,
|
|
||||||
...props
|
|
||||||
},
|
|
||||||
ref,
|
|
||||||
) => {
|
|
||||||
// Clamp value between 0 and 100
|
// Clamp value between 0 and 100
|
||||||
const clampedValue = Math.min(Math.max(value, 0), 100);
|
const clampedValue = Math.min(Math.max(value, 0), 100);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ import type { HTMLAttributes } from "react";
|
||||||
/**
|
/**
|
||||||
* ProgressBar component props
|
* ProgressBar component props
|
||||||
*/
|
*/
|
||||||
export interface ProgressBarProps
|
export interface ProgressBarProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
||||||
extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
||||||
/**
|
/**
|
||||||
* Progress value (0-100)
|
* Progress value (0-100)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -292,7 +292,7 @@ describe("Section", () => {
|
||||||
</Section>,
|
</Section>,
|
||||||
);
|
);
|
||||||
expect(ref).toBeInstanceOf(HTMLElement);
|
expect(ref).toBeInstanceOf(HTMLElement);
|
||||||
expect(ref!.tagName).toBe("SECTION");
|
expect(ref?.tagName).toBe("SECTION");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -308,13 +308,7 @@ describe("Section", () => {
|
||||||
describe("Compound Scenarios", () => {
|
describe("Compound Scenarios", () => {
|
||||||
it("works with multiple variants combined", () => {
|
it("works with multiple variants combined", () => {
|
||||||
render(
|
render(
|
||||||
<Section
|
<Section spacing="lg" background="blueprint" container="xl" centered data-testid="section">
|
||||||
spacing="lg"
|
|
||||||
background="blueprint"
|
|
||||||
container="xl"
|
|
||||||
centered
|
|
||||||
data-testid="section"
|
|
||||||
>
|
|
||||||
Content
|
Content
|
||||||
</Section>,
|
</Section>,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,7 @@ describe("Tabs", () => {
|
||||||
it("renders tabs container", () => {
|
it("renders tabs container", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(
|
||||||
<Tabs
|
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} data-testid="tabs" />,
|
||||||
tabs={mockTabs}
|
|
||||||
activeTab="all"
|
|
||||||
onTabChange={handleChange}
|
|
||||||
data-testid="tabs"
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
const tabs = screen.getByTestId("tabs");
|
const tabs = screen.getByTestId("tabs");
|
||||||
expect(tabs).toBeInTheDocument();
|
expect(tabs).toBeInTheDocument();
|
||||||
|
|
@ -29,12 +24,7 @@ describe("Tabs", () => {
|
||||||
it("has tablist role", () => {
|
it("has tablist role", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(
|
||||||
<Tabs
|
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} data-testid="tabs" />,
|
||||||
tabs={mockTabs}
|
|
||||||
activeTab="all"
|
|
||||||
onTabChange={handleChange}
|
|
||||||
data-testid="tabs"
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
const tabs = screen.getByTestId("tabs");
|
const tabs = screen.getByTestId("tabs");
|
||||||
expect(tabs).toHaveAttribute("role", "tablist");
|
expect(tabs).toHaveAttribute("role", "tablist");
|
||||||
|
|
@ -42,9 +32,7 @@ describe("Tabs", () => {
|
||||||
|
|
||||||
it("renders all tabs", () => {
|
it("renders all tabs", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId("tab-all")).toBeInTheDocument();
|
expect(screen.getByTestId("tab-all")).toBeInTheDocument();
|
||||||
expect(screen.getByTestId("tab-featured")).toBeInTheDocument();
|
expect(screen.getByTestId("tab-featured")).toBeInTheDocument();
|
||||||
expect(screen.getByTestId("tab-recent")).toBeInTheDocument();
|
expect(screen.getByTestId("tab-recent")).toBeInTheDocument();
|
||||||
|
|
@ -52,9 +40,7 @@ describe("Tabs", () => {
|
||||||
|
|
||||||
it("renders tab labels", () => {
|
it("renders tab labels", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
expect(screen.getByText("All Tools")).toBeInTheDocument();
|
expect(screen.getByText("All Tools")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Featured")).toBeInTheDocument();
|
expect(screen.getByText("Featured")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Recent")).toBeInTheDocument();
|
expect(screen.getByText("Recent")).toBeInTheDocument();
|
||||||
|
|
@ -62,9 +48,7 @@ describe("Tabs", () => {
|
||||||
|
|
||||||
it("renders tabs as buttons", () => {
|
it("renders tabs as buttons", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
const tab = screen.getByTestId("tab-all");
|
const tab = screen.getByTestId("tab-all");
|
||||||
expect(tab.tagName).toBe("BUTTON");
|
expect(tab.tagName).toBe("BUTTON");
|
||||||
expect(tab).toHaveAttribute("type", "button");
|
expect(tab).toHaveAttribute("type", "button");
|
||||||
|
|
@ -74,32 +58,15 @@ describe("Tabs", () => {
|
||||||
describe("Active State", () => {
|
describe("Active State", () => {
|
||||||
it("marks active tab with aria-selected", () => {
|
it("marks active tab with aria-selected", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="featured" onTabChange={handleChange} />);
|
||||||
<Tabs
|
expect(screen.getByTestId("tab-featured")).toHaveAttribute("aria-selected", "true");
|
||||||
tabs={mockTabs}
|
expect(screen.getByTestId("tab-all")).toHaveAttribute("aria-selected", "false");
|
||||||
activeTab="featured"
|
expect(screen.getByTestId("tab-recent")).toHaveAttribute("aria-selected", "false");
|
||||||
onTabChange={handleChange}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId("tab-featured")).toHaveAttribute(
|
|
||||||
"aria-selected",
|
|
||||||
"true",
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId("tab-all")).toHaveAttribute(
|
|
||||||
"aria-selected",
|
|
||||||
"false",
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId("tab-recent")).toHaveAttribute(
|
|
||||||
"aria-selected",
|
|
||||||
"false",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies active styling to active tab", () => {
|
it("applies active styling to active tab", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
const activeTab = screen.getByTestId("tab-all");
|
const activeTab = screen.getByTestId("tab-all");
|
||||||
expect(activeTab.className).toContain("text-foreground");
|
expect(activeTab.className).toContain("text-foreground");
|
||||||
expect(activeTab.className).toContain("border-primary");
|
expect(activeTab.className).toContain("border-primary");
|
||||||
|
|
@ -107,9 +74,7 @@ describe("Tabs", () => {
|
||||||
|
|
||||||
it("applies inactive styling to inactive tabs", () => {
|
it("applies inactive styling to inactive tabs", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
const inactiveTab = screen.getByTestId("tab-featured");
|
const inactiveTab = screen.getByTestId("tab-featured");
|
||||||
expect(inactiveTab.className).toContain("text-foreground-secondary");
|
expect(inactiveTab.className).toContain("text-foreground-secondary");
|
||||||
expect(inactiveTab.className).toContain("border-transparent");
|
expect(inactiveTab.className).toContain("border-transparent");
|
||||||
|
|
@ -119,18 +84,14 @@ describe("Tabs", () => {
|
||||||
describe("Click Handling", () => {
|
describe("Click Handling", () => {
|
||||||
it("calls onTabChange when tab is clicked", () => {
|
it("calls onTabChange when tab is clicked", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
screen.getByTestId("tab-featured").click();
|
screen.getByTestId("tab-featured").click();
|
||||||
expect(handleChange).toHaveBeenCalledWith("featured");
|
expect(handleChange).toHaveBeenCalledWith("featured");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls onTabChange with correct tab ID", () => {
|
it("calls onTabChange with correct tab ID", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
screen.getByTestId("tab-recent").click();
|
screen.getByTestId("tab-recent").click();
|
||||||
expect(handleChange).toHaveBeenCalledWith("recent");
|
expect(handleChange).toHaveBeenCalledWith("recent");
|
||||||
expect(handleChange).toHaveBeenCalledTimes(1);
|
expect(handleChange).toHaveBeenCalledTimes(1);
|
||||||
|
|
@ -138,9 +99,7 @@ describe("Tabs", () => {
|
||||||
|
|
||||||
it("allows clicking already active tab", () => {
|
it("allows clicking already active tab", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
screen.getByTestId("tab-all").click();
|
screen.getByTestId("tab-all").click();
|
||||||
expect(handleChange).toHaveBeenCalledWith("all");
|
expect(handleChange).toHaveBeenCalledWith("all");
|
||||||
});
|
});
|
||||||
|
|
@ -149,18 +108,14 @@ describe("Tabs", () => {
|
||||||
describe("Count Badges", () => {
|
describe("Count Badges", () => {
|
||||||
it("renders count badge when count is provided", () => {
|
it("renders count badge when count is provided", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
expect(screen.getByText("1234")).toBeInTheDocument();
|
expect(screen.getByText("1234")).toBeInTheDocument();
|
||||||
expect(screen.getByText("42")).toBeInTheDocument();
|
expect(screen.getByText("42")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not render count badge when count is undefined", () => {
|
it("does not render count badge when count is undefined", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
const recentTab = screen.getByTestId("tab-recent");
|
const recentTab = screen.getByTestId("tab-recent");
|
||||||
const badge = recentTab.querySelector("span[aria-label]");
|
const badge = recentTab.querySelector("span[aria-label]");
|
||||||
expect(badge).not.toBeInTheDocument();
|
expect(badge).not.toBeInTheDocument();
|
||||||
|
|
@ -168,27 +123,21 @@ describe("Tabs", () => {
|
||||||
|
|
||||||
it("count badge has aria-label", () => {
|
it("count badge has aria-label", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
const badge = screen.getByText("1234");
|
const badge = screen.getByText("1234");
|
||||||
expect(badge).toHaveAttribute("aria-label", "1234 items");
|
expect(badge).toHaveAttribute("aria-label", "1234 items");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies active styling to count badge on active tab", () => {
|
it("applies active styling to count badge on active tab", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
const activeBadge = screen.getByText("1234");
|
const activeBadge = screen.getByText("1234");
|
||||||
expect(activeBadge.className).toContain("text-foreground");
|
expect(activeBadge.className).toContain("text-foreground");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies inactive styling to count badge on inactive tab", () => {
|
it("applies inactive styling to count badge on inactive tab", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
const inactiveBadge = screen.getByText("42");
|
const inactiveBadge = screen.getByText("42");
|
||||||
expect(inactiveBadge.className).toContain("text-foreground-tertiary");
|
expect(inactiveBadge.className).toContain("text-foreground-tertiary");
|
||||||
});
|
});
|
||||||
|
|
@ -196,13 +145,7 @@ describe("Tabs", () => {
|
||||||
it("handles zero count", () => {
|
it("handles zero count", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
const tabsWithZero: Tab[] = [{ id: "empty", label: "Empty", count: 0 }];
|
const tabsWithZero: Tab[] = [{ id: "empty", label: "Empty", count: 0 }];
|
||||||
render(
|
render(<Tabs tabs={tabsWithZero} activeTab="empty" onTabChange={handleChange} />);
|
||||||
<Tabs
|
|
||||||
tabs={tabsWithZero}
|
|
||||||
activeTab="empty"
|
|
||||||
onTabChange={handleChange}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
expect(screen.getByText("0")).toBeInTheDocument();
|
expect(screen.getByText("0")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -210,14 +153,7 @@ describe("Tabs", () => {
|
||||||
describe("Size Variants", () => {
|
describe("Size Variants", () => {
|
||||||
it("applies small size", () => {
|
it("applies small size", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} size="sm" />);
|
||||||
<Tabs
|
|
||||||
tabs={mockTabs}
|
|
||||||
activeTab="all"
|
|
||||||
onTabChange={handleChange}
|
|
||||||
size="sm"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const tab = screen.getByTestId("tab-all");
|
const tab = screen.getByTestId("tab-all");
|
||||||
expect(tab.className).toContain("text-sm");
|
expect(tab.className).toContain("text-sm");
|
||||||
expect(tab.className).toContain("px-3");
|
expect(tab.className).toContain("px-3");
|
||||||
|
|
@ -226,14 +162,7 @@ describe("Tabs", () => {
|
||||||
|
|
||||||
it("applies medium size", () => {
|
it("applies medium size", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} size="md" />);
|
||||||
<Tabs
|
|
||||||
tabs={mockTabs}
|
|
||||||
activeTab="all"
|
|
||||||
onTabChange={handleChange}
|
|
||||||
size="md"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const tab = screen.getByTestId("tab-all");
|
const tab = screen.getByTestId("tab-all");
|
||||||
expect(tab.className).toContain("text-base");
|
expect(tab.className).toContain("text-base");
|
||||||
expect(tab.className).toContain("px-4");
|
expect(tab.className).toContain("px-4");
|
||||||
|
|
@ -242,14 +171,7 @@ describe("Tabs", () => {
|
||||||
|
|
||||||
it("applies large size", () => {
|
it("applies large size", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} size="lg" />);
|
||||||
<Tabs
|
|
||||||
tabs={mockTabs}
|
|
||||||
activeTab="all"
|
|
||||||
onTabChange={handleChange}
|
|
||||||
size="lg"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const tab = screen.getByTestId("tab-all");
|
const tab = screen.getByTestId("tab-all");
|
||||||
expect(tab.className).toContain("text-lg");
|
expect(tab.className).toContain("text-lg");
|
||||||
expect(tab.className).toContain("px-6");
|
expect(tab.className).toContain("px-6");
|
||||||
|
|
@ -258,9 +180,7 @@ describe("Tabs", () => {
|
||||||
|
|
||||||
it("uses md size by default", () => {
|
it("uses md size by default", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
const tab = screen.getByTestId("tab-all");
|
const tab = screen.getByTestId("tab-all");
|
||||||
expect(tab.className).toContain("text-base");
|
expect(tab.className).toContain("text-base");
|
||||||
expect(tab.className).toContain("px-4");
|
expect(tab.className).toContain("px-4");
|
||||||
|
|
@ -271,38 +191,23 @@ describe("Tabs", () => {
|
||||||
describe("Accessibility", () => {
|
describe("Accessibility", () => {
|
||||||
it('tabs have role="tab"', () => {
|
it('tabs have role="tab"', () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
const tab = screen.getByTestId("tab-all");
|
const tab = screen.getByTestId("tab-all");
|
||||||
expect(tab).toHaveAttribute("role", "tab");
|
expect(tab).toHaveAttribute("role", "tab");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("tabs have unique IDs", () => {
|
it("tabs have unique IDs", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId("tab-all")).toHaveAttribute("id", "tab-all");
|
expect(screen.getByTestId("tab-all")).toHaveAttribute("id", "tab-all");
|
||||||
expect(screen.getByTestId("tab-featured")).toHaveAttribute(
|
expect(screen.getByTestId("tab-featured")).toHaveAttribute("id", "tab-featured");
|
||||||
"id",
|
expect(screen.getByTestId("tab-recent")).toHaveAttribute("id", "tab-recent");
|
||||||
"tab-featured",
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId("tab-recent")).toHaveAttribute(
|
|
||||||
"id",
|
|
||||||
"tab-recent",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("tabs have aria-controls attribute", () => {
|
it("tabs have aria-controls attribute", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
expect(screen.getByTestId("tab-all")).toHaveAttribute("aria-controls", "tabpanel-all");
|
||||||
);
|
|
||||||
expect(screen.getByTestId("tab-all")).toHaveAttribute(
|
|
||||||
"aria-controls",
|
|
||||||
"tabpanel-all",
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId("tab-featured")).toHaveAttribute(
|
expect(screen.getByTestId("tab-featured")).toHaveAttribute(
|
||||||
"aria-controls",
|
"aria-controls",
|
||||||
"tabpanel-featured",
|
"tabpanel-featured",
|
||||||
|
|
@ -384,12 +289,7 @@ describe("Tabs", () => {
|
||||||
it("container includes base classes", () => {
|
it("container includes base classes", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(
|
||||||
<Tabs
|
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} data-testid="tabs" />,
|
||||||
tabs={mockTabs}
|
|
||||||
activeTab="all"
|
|
||||||
onTabChange={handleChange}
|
|
||||||
data-testid="tabs"
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
const tabs = screen.getByTestId("tabs");
|
const tabs = screen.getByTestId("tabs");
|
||||||
expect(tabs.className).toContain("flex");
|
expect(tabs.className).toContain("flex");
|
||||||
|
|
@ -400,9 +300,7 @@ describe("Tabs", () => {
|
||||||
|
|
||||||
it("tab buttons include base classes", () => {
|
it("tab buttons include base classes", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={mockTabs} activeTab="all" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
const tab = screen.getByTestId("tab-all");
|
const tab = screen.getByTestId("tab-all");
|
||||||
expect(tab.className).toContain("inline-flex");
|
expect(tab.className).toContain("inline-flex");
|
||||||
expect(tab.className).toContain("items-center");
|
expect(tab.className).toContain("items-center");
|
||||||
|
|
@ -416,14 +314,7 @@ describe("Tabs", () => {
|
||||||
describe("Compound Scenarios", () => {
|
describe("Compound Scenarios", () => {
|
||||||
it("works correctly with large size and active tab with count", () => {
|
it("works correctly with large size and active tab with count", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
render(
|
render(<Tabs tabs={mockTabs} activeTab="featured" onTabChange={handleChange} size="lg" />);
|
||||||
<Tabs
|
|
||||||
tabs={mockTabs}
|
|
||||||
activeTab="featured"
|
|
||||||
onTabChange={handleChange}
|
|
||||||
size="lg"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const tab = screen.getByTestId("tab-featured");
|
const tab = screen.getByTestId("tab-featured");
|
||||||
expect(tab.className).toContain("text-lg");
|
expect(tab.className).toContain("text-lg");
|
||||||
expect(tab.className).toContain("px-6");
|
expect(tab.className).toContain("px-6");
|
||||||
|
|
@ -437,18 +328,14 @@ describe("Tabs", () => {
|
||||||
it("handles single tab", () => {
|
it("handles single tab", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
const singleTab: Tab[] = [{ id: "only", label: "Only Tab" }];
|
const singleTab: Tab[] = [{ id: "only", label: "Only Tab" }];
|
||||||
render(
|
render(<Tabs tabs={singleTab} activeTab="only" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={singleTab} activeTab="only" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId("tab-only")).toBeInTheDocument();
|
expect(screen.getByTestId("tab-only")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Only Tab")).toBeInTheDocument();
|
expect(screen.getByText("Only Tab")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles empty tabs array", () => {
|
it("handles empty tabs array", () => {
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
const { container } = render(
|
const { container } = render(<Tabs tabs={[]} activeTab="" onTabChange={handleChange} />);
|
||||||
<Tabs tabs={[]} activeTab="" onTabChange={handleChange} />,
|
|
||||||
);
|
|
||||||
const buttons = container.querySelectorAll("button");
|
const buttons = container.querySelectorAll("button");
|
||||||
expect(buttons.length).toBe(0);
|
expect(buttons.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
import { cn } from "@tpmjs/utils/cn";
|
import { cn } from "@tpmjs/utils/cn";
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
import type { Tab, TabsProps } from "./types";
|
import type { Tab, TabsProps } from "./types";
|
||||||
import {
|
import { tabButtonVariants, tabCountVariants, tabsContainerVariants } from "./variants";
|
||||||
tabButtonVariants,
|
|
||||||
tabCountVariants,
|
|
||||||
tabsContainerVariants,
|
|
||||||
} from "./variants";
|
|
||||||
|
|
||||||
// Re-export types for consumers
|
// Re-export types for consumers
|
||||||
export type { Tab, TabsProps };
|
export type { Tab, TabsProps };
|
||||||
|
|
@ -40,15 +36,7 @@ export type { Tab, TabsProps };
|
||||||
*/
|
*/
|
||||||
export const Tabs = forwardRef<HTMLDivElement, TabsProps>(
|
export const Tabs = forwardRef<HTMLDivElement, TabsProps>(
|
||||||
(
|
(
|
||||||
{
|
{ className, tabs, activeTab, onTabChange, size = "md", variant = "default", ...props },
|
||||||
className,
|
|
||||||
tabs,
|
|
||||||
activeTab,
|
|
||||||
onTabChange,
|
|
||||||
size = "md",
|
|
||||||
variant = "default",
|
|
||||||
...props
|
|
||||||
},
|
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,7 @@ export interface Tab {
|
||||||
/**
|
/**
|
||||||
* Tabs component props
|
* Tabs component props
|
||||||
*/
|
*/
|
||||||
export interface TabsProps
|
export interface TabsProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
|
||||||
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
|
|
||||||
/**
|
/**
|
||||||
* Array of tabs to display
|
* Array of tabs to display
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,9 @@ export type VariantConfig<T extends Record<string, Record<string, string>>> = {
|
||||||
/**
|
/**
|
||||||
* Props type for consuming variant functions
|
* Props type for consuming variant functions
|
||||||
*/
|
*/
|
||||||
export type VariantProps<T extends Record<string, Record<string, string>>> =
|
export type VariantProps<T extends Record<string, Record<string, string>>> = Partial<{
|
||||||
Partial<{
|
[K in keyof T]: keyof T[K];
|
||||||
[K in keyof T]: keyof T[K];
|
}>;
|
||||||
}>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a type-safe variant composition function
|
* Creates a type-safe variant composition function
|
||||||
|
|
@ -68,9 +67,9 @@ export type VariantProps<T extends Record<string, Record<string, string>>> =
|
||||||
* @param config - Variant configuration with base, variants, compound variants, and defaults
|
* @param config - Variant configuration with base, variants, compound variants, and defaults
|
||||||
* @returns Function that accepts variant props and returns composed className string
|
* @returns Function that accepts variant props and returns composed className string
|
||||||
*/
|
*/
|
||||||
export function createVariants<
|
export function createVariants<T extends Record<string, Record<string, string>>>(
|
||||||
T extends Record<string, Record<string, string>>,
|
config: VariantConfig<T>,
|
||||||
>(config: VariantConfig<T>) {
|
) {
|
||||||
return function getVariantClasses(props?: VariantProps<T>): string {
|
return function getVariantClasses(props?: VariantProps<T>): string {
|
||||||
const classes: string[] = [config.base];
|
const classes: string[] = [config.base];
|
||||||
|
|
||||||
|
|
@ -83,11 +82,7 @@ export function createVariants<
|
||||||
// Apply variant classes
|
// Apply variant classes
|
||||||
for (const [key, value] of Object.entries(mergedProps)) {
|
for (const [key, value] of Object.entries(mergedProps)) {
|
||||||
const valueStr = value as string;
|
const valueStr = value as string;
|
||||||
if (
|
if (value !== undefined && value !== null && config.variants[key]?.[valueStr]) {
|
||||||
value !== undefined &&
|
|
||||||
value !== null &&
|
|
||||||
config.variants[key]?.[valueStr]
|
|
||||||
) {
|
|
||||||
classes.push(config.variants[key][valueStr]);
|
classes.push(config.variants[key][valueStr]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -96,8 +91,7 @@ export function createVariants<
|
||||||
if (config.compoundVariants) {
|
if (config.compoundVariants) {
|
||||||
for (const { conditions, className } of config.compoundVariants) {
|
for (const { conditions, className } of config.compoundVariants) {
|
||||||
const matches = Object.entries(conditions).every(
|
const matches = Object.entries(conditions).every(
|
||||||
([key, value]) =>
|
([key, value]) => mergedProps[key as keyof typeof mergedProps] === value,
|
||||||
mergedProps[key as keyof typeof mergedProps] === value,
|
|
||||||
);
|
);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
classes.push(className);
|
classes.push(className);
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
import '@testing-library/jest-dom/vitest';
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"extends": "@tpmjs/tsconfig/react-library.json",
|
"extends": "@tpmjs/tsconfig/react-library.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"incremental": false,
|
"incremental": false,
|
||||||
"composite": false
|
"composite": false
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
environment: 'happy-dom',
|
environment: "happy-dom",
|
||||||
setupFiles: ['./src/test-setup.ts'],
|
setupFiles: ["./src/test-setup.ts"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue