Card component
Compound API (Root/Header/Content/Footer/Title/Description) with data-scope/data-part attributes, CardPropsSchema, and CardMeta. 4 tests passing.
This commit is contained in:
parent
01286d8b07
commit
5c503ee9ef
13
packages/core/src/components/card/card-content.tsx
Normal file
13
packages/core/src/components/card/card-content.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { splitProps } from "solid-js";
|
||||||
|
import type { CardContentProps } from "./card.props";
|
||||||
|
|
||||||
|
/** The main content area of a Card. */
|
||||||
|
export function CardContent(props: CardContentProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children"]);
|
||||||
|
return (
|
||||||
|
<div data-scope="card" data-part="content" {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
packages/core/src/components/card/card-description.tsx
Normal file
13
packages/core/src/components/card/card-description.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { splitProps } from "solid-js";
|
||||||
|
import type { CardDescriptionProps } from "./card.props";
|
||||||
|
|
||||||
|
/** A supporting description paragraph within a Card header. */
|
||||||
|
export function CardDescription(props: CardDescriptionProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children"]);
|
||||||
|
return (
|
||||||
|
<p data-scope="card" data-part="description" {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
packages/core/src/components/card/card-footer.tsx
Normal file
13
packages/core/src/components/card/card-footer.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { splitProps } from "solid-js";
|
||||||
|
import type { CardFooterProps } from "./card.props";
|
||||||
|
|
||||||
|
/** The footer section of a Card, typically containing actions or metadata. */
|
||||||
|
export function CardFooter(props: CardFooterProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children"]);
|
||||||
|
return (
|
||||||
|
<div data-scope="card" data-part="footer" {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
packages/core/src/components/card/card-header.tsx
Normal file
13
packages/core/src/components/card/card-header.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { splitProps } from "solid-js";
|
||||||
|
import type { CardHeaderProps } from "./card.props";
|
||||||
|
|
||||||
|
/** The header section of a Card, typically containing a title and description. */
|
||||||
|
export function CardHeader(props: CardHeaderProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children"]);
|
||||||
|
return (
|
||||||
|
<div data-scope="card" data-part="header" {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
packages/core/src/components/card/card-root.tsx
Normal file
13
packages/core/src/components/card/card-root.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { splitProps } from "solid-js";
|
||||||
|
import type { CardRootProps } from "./card.props";
|
||||||
|
|
||||||
|
/** The root container element for a Card. */
|
||||||
|
export function CardRoot(props: CardRootProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children"]);
|
||||||
|
return (
|
||||||
|
<div data-scope="card" data-part="root" {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
packages/core/src/components/card/card-title.tsx
Normal file
13
packages/core/src/components/card/card-title.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { splitProps } from "solid-js";
|
||||||
|
import type { CardTitleProps } from "./card.props";
|
||||||
|
|
||||||
|
/** The heading element within a Card header. */
|
||||||
|
export function CardTitle(props: CardTitleProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children"]);
|
||||||
|
return (
|
||||||
|
<h3 data-scope="card" data-part="title" {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
packages/core/src/components/card/card.props.ts
Normal file
18
packages/core/src/components/card/card.props.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import type { ComponentMeta } from "../../meta";
|
||||||
|
|
||||||
|
export const CardPropsSchema = z.object({});
|
||||||
|
export interface CardRootProps extends JSX.HTMLAttributes<HTMLDivElement> { children?: JSX.Element; }
|
||||||
|
export interface CardHeaderProps extends JSX.HTMLAttributes<HTMLDivElement> { children?: JSX.Element; }
|
||||||
|
export interface CardContentProps extends JSX.HTMLAttributes<HTMLDivElement> { children?: JSX.Element; }
|
||||||
|
export interface CardFooterProps extends JSX.HTMLAttributes<HTMLDivElement> { children?: JSX.Element; }
|
||||||
|
export interface CardTitleProps extends JSX.HTMLAttributes<HTMLHeadingElement> { children?: JSX.Element; }
|
||||||
|
export interface CardDescriptionProps extends JSX.HTMLAttributes<HTMLParagraphElement> { children?: JSX.Element; }
|
||||||
|
|
||||||
|
export const CardMeta: ComponentMeta = {
|
||||||
|
name: "Card",
|
||||||
|
description: "Grouped content container with header, body, and footer sections",
|
||||||
|
parts: ["Root", "Header", "Content", "Footer", "Title", "Description"] as const,
|
||||||
|
requiredParts: ["Root", "Content"] as const,
|
||||||
|
} as const;
|
||||||
15
packages/core/src/components/card/index.ts
Normal file
15
packages/core/src/components/card/index.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { CardRoot } from "./card-root";
|
||||||
|
import { CardHeader } from "./card-header";
|
||||||
|
import { CardContent } from "./card-content";
|
||||||
|
import { CardFooter } from "./card-footer";
|
||||||
|
import { CardTitle } from "./card-title";
|
||||||
|
import { CardDescription } from "./card-description";
|
||||||
|
export const Card = Object.assign(CardRoot, {
|
||||||
|
Header: CardHeader,
|
||||||
|
Content: CardContent,
|
||||||
|
Footer: CardFooter,
|
||||||
|
Title: CardTitle,
|
||||||
|
Description: CardDescription,
|
||||||
|
});
|
||||||
|
export type { CardRootProps, CardHeaderProps, CardContentProps, CardFooterProps, CardTitleProps, CardDescriptionProps } from "./card.props";
|
||||||
|
export { CardPropsSchema, CardMeta } from "./card.props";
|
||||||
40
packages/core/tests/components/card/card.test.tsx
Normal file
40
packages/core/tests/components/card/card.test.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { render, screen } from "@solidjs/testing-library";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { Card } from "../../../src/components/card/index";
|
||||||
|
import { CardPropsSchema, CardMeta } from "../../../src/components/card/card.props";
|
||||||
|
|
||||||
|
describe("Card", () => {
|
||||||
|
it("renders with compound API", () => {
|
||||||
|
render(() => (
|
||||||
|
<Card>
|
||||||
|
<Card.Header>
|
||||||
|
<Card.Title>Title</Card.Title>
|
||||||
|
<Card.Description>Description</Card.Description>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content>Body content</Card.Content>
|
||||||
|
<Card.Footer>Footer</Card.Footer>
|
||||||
|
</Card>
|
||||||
|
));
|
||||||
|
expect(screen.getByText("Title")).toBeTruthy();
|
||||||
|
expect(screen.getByText("Description")).toBeTruthy();
|
||||||
|
expect(screen.getByText("Body content")).toBeTruthy();
|
||||||
|
expect(screen.getByText("Footer")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders as div element", () => {
|
||||||
|
render(() => <Card data-testid="card"><Card.Content>Content</Card.Content></Card>);
|
||||||
|
expect(screen.getByTestId("card").tagName).toBe("DIV");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("schema validates empty props", () => {
|
||||||
|
expect(CardPropsSchema.safeParse({}).success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("meta has all required fields", () => {
|
||||||
|
expect(CardMeta.name).toBe("Card");
|
||||||
|
expect(CardMeta.parts).toContain("Root");
|
||||||
|
expect(CardMeta.parts).toContain("Header");
|
||||||
|
expect(CardMeta.parts).toContain("Content");
|
||||||
|
expect(CardMeta.parts).toContain("Footer");
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user