Migrate basic components to Zod props
This commit is contained in:
parent
38ef3b0934
commit
c4547473a4
11
packages/core/src/components/alert/alert.props.ts
Normal file
11
packages/core/src/components/alert/alert.props.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import type { ComponentMeta } from "../../meta";
|
||||||
|
export const AlertPropsSchema = z.object({});
|
||||||
|
export interface AlertProps extends z.infer<typeof AlertPropsSchema>, Omit<JSX.HTMLAttributes<HTMLDivElement>, keyof z.infer<typeof AlertPropsSchema>> { children?: JSX.Element; }
|
||||||
|
export const AlertMeta: ComponentMeta = {
|
||||||
|
name: "Alert",
|
||||||
|
description: "Inline status message for important information, warnings, errors, or success states",
|
||||||
|
parts: ["Root", "Title", "Description"] as const,
|
||||||
|
requiredParts: ["Root"] as const,
|
||||||
|
} as const;
|
||||||
@ -1,9 +1,7 @@
|
|||||||
import type { JSX } from "solid-js";
|
import type { JSX } from "solid-js";
|
||||||
import { splitProps } from "solid-js";
|
import { splitProps } from "solid-js";
|
||||||
|
import type { AlertProps } from "./alert.props";
|
||||||
export interface AlertProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
export type { AlertProps } from "./alert.props";
|
||||||
children?: JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** An alert element that announces important messages to screen readers via role="alert". */
|
/** An alert element that announces important messages to screen readers via role="alert". */
|
||||||
export function Alert(props: AlertProps): JSX.Element {
|
export function Alert(props: AlertProps): JSX.Element {
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export { Alert } from "./alert";
|
export { Alert } from "./alert";
|
||||||
export type { AlertProps } from "./alert";
|
export type { AlertProps } from "./alert";
|
||||||
|
export { AlertPropsSchema, AlertMeta } from "./alert.props";
|
||||||
|
|||||||
11
packages/core/src/components/badge/badge.props.ts
Normal file
11
packages/core/src/components/badge/badge.props.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import type { ComponentMeta } from "../../meta";
|
||||||
|
export const BadgePropsSchema = z.object({});
|
||||||
|
export interface BadgeProps extends z.infer<typeof BadgePropsSchema>, Omit<JSX.HTMLAttributes<HTMLSpanElement>, keyof z.infer<typeof BadgePropsSchema>> { children?: JSX.Element; }
|
||||||
|
export const BadgeMeta: ComponentMeta = {
|
||||||
|
name: "Badge",
|
||||||
|
description: "Small status indicator label, typically used for counts, tags, or status",
|
||||||
|
parts: ["Badge"] as const,
|
||||||
|
requiredParts: ["Badge"] as const,
|
||||||
|
} as const;
|
||||||
@ -1,9 +1,7 @@
|
|||||||
import type { JSX } from "solid-js";
|
import type { JSX } from "solid-js";
|
||||||
import { splitProps } from "solid-js";
|
import { splitProps } from "solid-js";
|
||||||
|
import type { BadgeProps } from "./badge.props";
|
||||||
export interface BadgeProps extends JSX.HTMLAttributes<HTMLSpanElement> {
|
export type { BadgeProps } from "./badge.props";
|
||||||
children?: JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A status badge that announces its content to screen readers via role="status". */
|
/** A status badge that announces its content to screen readers via role="status". */
|
||||||
export function Badge(props: BadgeProps): JSX.Element {
|
export function Badge(props: BadgeProps): JSX.Element {
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export { Badge } from "./badge";
|
export { Badge } from "./badge";
|
||||||
export type { BadgeProps } from "./badge";
|
export type { BadgeProps } from "./badge";
|
||||||
|
export { BadgePropsSchema, BadgeMeta } from "./badge.props";
|
||||||
|
|||||||
14
packages/core/src/components/button/button.props.ts
Normal file
14
packages/core/src/components/button/button.props.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import type { ComponentMeta } from "../../meta";
|
||||||
|
export const ButtonPropsSchema = z.object({
|
||||||
|
type: z.enum(["button", "submit", "reset"]).optional().describe("Button type attribute. Defaults to 'button' to prevent accidental form submission."),
|
||||||
|
disabled: z.boolean().optional().describe("When true, disables the button and prevents click handlers."),
|
||||||
|
});
|
||||||
|
export interface ButtonProps extends z.infer<typeof ButtonPropsSchema>, Omit<JSX.ButtonHTMLAttributes<HTMLButtonElement>, keyof z.infer<typeof ButtonPropsSchema>> { children?: JSX.Element; }
|
||||||
|
export const ButtonMeta: ComponentMeta = {
|
||||||
|
name: "Button",
|
||||||
|
description: "Clickable element that triggers an action. Defaults type to 'button' to prevent form submission",
|
||||||
|
parts: ["Button"] as const,
|
||||||
|
requiredParts: ["Button"] as const,
|
||||||
|
} as const;
|
||||||
@ -1,14 +1,7 @@
|
|||||||
import type { JSX } from "solid-js";
|
import type { JSX } from "solid-js";
|
||||||
import { splitProps } from "solid-js";
|
import { splitProps } from "solid-js";
|
||||||
|
import type { ButtonProps } from "./button.props";
|
||||||
/** Props for the accessible Button component. */
|
export type { ButtonProps } from "./button.props";
|
||||||
export interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
||||||
/** Button type attribute. @default "button" */
|
|
||||||
type?: "button" | "submit" | "reset" | undefined;
|
|
||||||
/** When true, disables the button and prevents click handlers. */
|
|
||||||
disabled?: boolean | undefined;
|
|
||||||
children?: JSX.Element | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An accessible button component.
|
* An accessible button component.
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export { Button } from "./button";
|
export { Button } from "./button";
|
||||||
export type { ButtonProps } from "./button";
|
export type { ButtonProps } from "./button";
|
||||||
|
export { ButtonPropsSchema, ButtonMeta } from "./button.props";
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export { Link } from "./link";
|
export { Link } from "./link";
|
||||||
export type { LinkProps } from "./link";
|
export type { LinkProps } from "./link";
|
||||||
|
export { LinkPropsSchema, LinkMeta } from "./link.props";
|
||||||
|
|||||||
15
packages/core/src/components/link/link.props.ts
Normal file
15
packages/core/src/components/link/link.props.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import type { ComponentMeta } from "../../meta";
|
||||||
|
export const LinkPropsSchema = z.object({
|
||||||
|
href: z.string().optional().describe("The URL the link navigates to. Removed from the DOM when disabled."),
|
||||||
|
external: z.boolean().optional().describe("When true, opens the link in a new tab with rel='noopener noreferrer'."),
|
||||||
|
disabled: z.boolean().optional().describe("When true, prevents navigation and marks the link as aria-disabled."),
|
||||||
|
});
|
||||||
|
export interface LinkProps extends z.infer<typeof LinkPropsSchema>, Omit<JSX.AnchorHTMLAttributes<HTMLAnchorElement>, keyof z.infer<typeof LinkPropsSchema>> { children?: JSX.Element; }
|
||||||
|
export const LinkMeta: ComponentMeta = {
|
||||||
|
name: "Link",
|
||||||
|
description: "Navigation anchor element with external link and disabled support",
|
||||||
|
parts: ["Link"] as const,
|
||||||
|
requiredParts: ["Link"] as const,
|
||||||
|
} as const;
|
||||||
@ -1,14 +1,7 @@
|
|||||||
import type { JSX } from "solid-js";
|
import type { JSX } from "solid-js";
|
||||||
import { splitProps } from "solid-js";
|
import { splitProps } from "solid-js";
|
||||||
|
import type { LinkProps } from "./link.props";
|
||||||
/** Props for the accessible Link component. */
|
export type { LinkProps } from "./link.props";
|
||||||
export interface LinkProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
||||||
/** The URL the link navigates to. Removed when disabled. */
|
|
||||||
href?: string | undefined;
|
|
||||||
/** When true, prevents navigation and marks the link as aria-disabled. */
|
|
||||||
disabled?: boolean | undefined;
|
|
||||||
children?: JSX.Element | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An accessible link component.
|
* An accessible link component.
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export { Progress } from "./progress";
|
export { Progress } from "./progress";
|
||||||
export type { ProgressProps } from "./progress";
|
export type { ProgressProps } from "./progress";
|
||||||
|
export { ProgressRootPropsSchema, ProgressMeta } from "./progress.props";
|
||||||
|
|||||||
15
packages/core/src/components/progress/progress.props.ts
Normal file
15
packages/core/src/components/progress/progress.props.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import type { ComponentMeta } from "../../meta";
|
||||||
|
export const ProgressRootPropsSchema = z.object({
|
||||||
|
value: z.number().optional().describe("Current progress value. Omit or pass null for indeterminate state."),
|
||||||
|
max: z.number().optional().describe("Maximum value for the progress bar. Defaults to 100."),
|
||||||
|
getValueLabel: z.function(z.tuple([z.number(), z.number()]), z.string()).optional().describe("Custom function to generate the aria-valuetext label. Receives (value, max)."),
|
||||||
|
});
|
||||||
|
export interface ProgressRootProps extends z.infer<typeof ProgressRootPropsSchema>, Omit<JSX.HTMLAttributes<HTMLDivElement>, keyof z.infer<typeof ProgressRootPropsSchema>> { children?: JSX.Element; }
|
||||||
|
export const ProgressMeta: ComponentMeta = {
|
||||||
|
name: "Progress",
|
||||||
|
description: "Visual indicator showing completion progress of a task or operation",
|
||||||
|
parts: ["Root", "Track", "Fill", "Label"] as const,
|
||||||
|
requiredParts: ["Root"] as const,
|
||||||
|
} as const;
|
||||||
@ -1,14 +1,7 @@
|
|||||||
import type { JSX } from "solid-js";
|
import type { JSX } from "solid-js";
|
||||||
import { splitProps } from "solid-js";
|
import { splitProps } from "solid-js";
|
||||||
|
import type { ProgressRootProps as ProgressProps } from "./progress.props";
|
||||||
export interface ProgressProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
export type { ProgressRootProps as ProgressProps } from "./progress.props";
|
||||||
/** Current value. Pass null for indeterminate. */
|
|
||||||
value?: number | null;
|
|
||||||
/** Maximum value. @default 100 */
|
|
||||||
max?: number;
|
|
||||||
/** Custom label function for aria-valuetext. */
|
|
||||||
getValueLabel?: (value: number, max: number) => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the progress of a task. Supports determinate and indeterminate states.
|
* Displays the progress of a task. Supports determinate and indeterminate states.
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export { Skeleton } from "./skeleton";
|
export { Skeleton } from "./skeleton";
|
||||||
export type { SkeletonProps } from "./skeleton";
|
export type { SkeletonProps } from "./skeleton";
|
||||||
|
export { SkeletonPropsSchema, SkeletonMeta } from "./skeleton.props";
|
||||||
|
|||||||
16
packages/core/src/components/skeleton/skeleton.props.ts
Normal file
16
packages/core/src/components/skeleton/skeleton.props.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import type { ComponentMeta } from "../../meta";
|
||||||
|
export const SkeletonPropsSchema = z.object({
|
||||||
|
visible: z.boolean().optional().describe("Whether the skeleton is visible (animating). Defaults to true."),
|
||||||
|
circle: z.boolean().optional().describe("Render as a circle. Defaults to false."),
|
||||||
|
width: z.string().optional().describe("Width in CSS units, e.g. '100px' or '50%'."),
|
||||||
|
height: z.string().optional().describe("Height in CSS units, e.g. '20px' or '2rem'."),
|
||||||
|
});
|
||||||
|
export interface SkeletonProps extends z.infer<typeof SkeletonPropsSchema>, Omit<JSX.HTMLAttributes<HTMLDivElement>, keyof z.infer<typeof SkeletonPropsSchema>> { children?: JSX.Element; }
|
||||||
|
export const SkeletonMeta: ComponentMeta = {
|
||||||
|
name: "Skeleton",
|
||||||
|
description: "Placeholder loading indicator that mimics the shape of content being loaded",
|
||||||
|
parts: ["Skeleton"] as const,
|
||||||
|
requiredParts: ["Skeleton"] as const,
|
||||||
|
} as const;
|
||||||
@ -1,16 +1,8 @@
|
|||||||
import type { JSX } from "solid-js";
|
import type { JSX } from "solid-js";
|
||||||
import { splitProps } from "solid-js";
|
import { splitProps } from "solid-js";
|
||||||
|
|
||||||
export interface SkeletonProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
import type { SkeletonProps } from "./skeleton.props";
|
||||||
/** Whether the skeleton is visible (animating). @default true */
|
export type { SkeletonProps } from "./skeleton.props";
|
||||||
visible?: boolean | undefined;
|
|
||||||
/** Render as a circle. @default false */
|
|
||||||
circle?: boolean | undefined;
|
|
||||||
/** Width in CSS units. */
|
|
||||||
width?: string | undefined;
|
|
||||||
/** Height in CSS units. */
|
|
||||||
height?: string | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A loading placeholder skeleton with data attributes for styling. */
|
/** A loading placeholder skeleton with data attributes for styling. */
|
||||||
export function Skeleton(props: SkeletonProps): JSX.Element {
|
export function Skeleton(props: SkeletonProps): JSX.Element {
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export { Toggle } from "./toggle";
|
export { Toggle } from "./toggle";
|
||||||
export type { ToggleProps } from "./toggle";
|
export type { ToggleProps } from "./toggle";
|
||||||
|
export { TogglePropsSchema, ToggleMeta } from "./toggle.props";
|
||||||
|
|||||||
15
packages/core/src/components/toggle/toggle.props.ts
Normal file
15
packages/core/src/components/toggle/toggle.props.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import type { ComponentMeta } from "../../meta";
|
||||||
|
export const TogglePropsSchema = z.object({
|
||||||
|
pressed: z.boolean().optional().describe("Controlled pressed state. When provided, the component is in controlled mode."),
|
||||||
|
defaultPressed: z.boolean().optional().describe("Default pressed state for uncontrolled usage. Defaults to false."),
|
||||||
|
disabled: z.boolean().optional().describe("When true, disables the toggle and prevents state changes."),
|
||||||
|
});
|
||||||
|
export interface ToggleProps extends z.infer<typeof TogglePropsSchema>, Omit<JSX.ButtonHTMLAttributes<HTMLButtonElement>, keyof z.infer<typeof TogglePropsSchema>> { onPressedChange?: (pressed: boolean) => void; children?: JSX.Element; }
|
||||||
|
export const ToggleMeta: ComponentMeta = {
|
||||||
|
name: "Toggle",
|
||||||
|
description: "Two-state button that can be toggled on or off",
|
||||||
|
parts: ["Toggle"] as const,
|
||||||
|
requiredParts: ["Toggle"] as const,
|
||||||
|
} as const;
|
||||||
@ -1,16 +1,8 @@
|
|||||||
import type { JSX } from "solid-js";
|
import type { JSX } from "solid-js";
|
||||||
import { splitProps } from "solid-js";
|
import { splitProps } from "solid-js";
|
||||||
import { createControllableSignal } from "../../primitives/create-controllable-signal";
|
import { createControllableSignal } from "../../primitives/create-controllable-signal";
|
||||||
|
import type { ToggleProps } from "./toggle.props";
|
||||||
export interface ToggleProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
export type { ToggleProps } from "./toggle.props";
|
||||||
/** Controlled pressed state. */
|
|
||||||
pressed?: boolean;
|
|
||||||
/** Default pressed state (uncontrolled). @default false */
|
|
||||||
defaultPressed?: boolean;
|
|
||||||
/** Called when pressed state changes. */
|
|
||||||
onPressedChange?: (pressed: boolean) => void;
|
|
||||||
children?: JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A two-state button that can be toggled on and off.
|
* A two-state button that can be toggled on and off.
|
||||||
|
|||||||
38
packages/core/tests/schemas/collection-components.test.ts
Normal file
38
packages/core/tests/schemas/collection-components.test.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { SelectRootPropsSchema, SelectMeta } from "../../src/components/select/select.props";
|
||||||
|
import { ComboboxRootPropsSchema, ComboboxMeta } from "../../src/components/combobox/combobox.props";
|
||||||
|
import { DropdownMenuRootPropsSchema, DropdownMenuMeta } from "../../src/components/dropdown-menu/dropdown-menu.props";
|
||||||
|
import { ListboxRootPropsSchema, ListboxMeta } from "../../src/components/listbox/listbox.props";
|
||||||
|
import { SeparatorPropsSchema, SeparatorMeta } from "../../src/components/separator/separator.props";
|
||||||
|
import { PaginationRootPropsSchema, PaginationMeta } from "../../src/components/pagination/pagination.props";
|
||||||
|
describe("Collection component schemas", () => {
|
||||||
|
it("Select validates", () => {
|
||||||
|
expect(SelectRootPropsSchema.safeParse({ value: "opt1", placeholder: "Choose..." }).success).toBe(true);
|
||||||
|
});
|
||||||
|
it("Combobox validates", () => {
|
||||||
|
expect(ComboboxRootPropsSchema.safeParse({ inputValue: "search", allowCustomValue: true }).success).toBe(true);
|
||||||
|
});
|
||||||
|
it("DropdownMenu validates", () => {
|
||||||
|
expect(DropdownMenuRootPropsSchema.safeParse({ open: true }).success).toBe(true);
|
||||||
|
});
|
||||||
|
it("Listbox validates", () => {
|
||||||
|
expect(ListboxRootPropsSchema.safeParse({ value: "a" }).success).toBe(true);
|
||||||
|
expect(ListboxRootPropsSchema.safeParse({ value: ["a", "b"], multiple: true }).success).toBe(true);
|
||||||
|
});
|
||||||
|
it("Separator validates", () => {
|
||||||
|
expect(SeparatorPropsSchema.safeParse({ orientation: "vertical" }).success).toBe(true);
|
||||||
|
expect(SeparatorPropsSchema.safeParse({ orientation: "angled" }).success).toBe(false);
|
||||||
|
});
|
||||||
|
it("Pagination validates", () => {
|
||||||
|
expect(PaginationRootPropsSchema.safeParse({ page: 3, count: 10, siblingCount: 2 }).success).toBe(true);
|
||||||
|
});
|
||||||
|
it("all Meta objects valid", () => {
|
||||||
|
const metas = [SelectMeta, ComboboxMeta, DropdownMenuMeta, ListboxMeta, SeparatorMeta, PaginationMeta];
|
||||||
|
for (const meta of metas) {
|
||||||
|
expect(meta.name).toBeTruthy();
|
||||||
|
expect(meta.description).toBeTruthy();
|
||||||
|
expect(meta.parts.length).toBeGreaterThan(0);
|
||||||
|
expect(meta.requiredParts.length).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
42
packages/core/tests/schemas/simple-components.test.ts
Normal file
42
packages/core/tests/schemas/simple-components.test.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { ButtonPropsSchema, ButtonMeta } from "../../src/components/button/button.props";
|
||||||
|
import { BadgeMeta } from "../../src/components/badge/badge.props";
|
||||||
|
import { AlertMeta } from "../../src/components/alert/alert.props";
|
||||||
|
import { SkeletonMeta } from "../../src/components/skeleton/skeleton.props";
|
||||||
|
import { LinkPropsSchema, LinkMeta } from "../../src/components/link/link.props";
|
||||||
|
import { TogglePropsSchema, ToggleMeta } from "../../src/components/toggle/toggle.props";
|
||||||
|
import { ProgressRootPropsSchema, ProgressMeta } from "../../src/components/progress/progress.props";
|
||||||
|
|
||||||
|
describe("Simple component schemas", () => {
|
||||||
|
it("Button schema validates", () => {
|
||||||
|
const validBtn = ButtonPropsSchema.safeParse({ type: "submit" });
|
||||||
|
const invalidBtn = ButtonPropsSchema.safeParse({ type: "invalid" });
|
||||||
|
expect(validBtn.success).toBe(true);
|
||||||
|
expect(invalidBtn.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Link schema validates", () => {
|
||||||
|
const result = LinkPropsSchema.safeParse({ href: "/about", external: true });
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Toggle schema validates", () => {
|
||||||
|
const result = TogglePropsSchema.safeParse({ pressed: true, disabled: false });
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Progress schema validates", () => {
|
||||||
|
const result = ProgressRootPropsSchema.safeParse({ value: 50, max: 100 });
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("all Meta objects have required fields", () => {
|
||||||
|
const metas = [ButtonMeta, BadgeMeta, AlertMeta, SkeletonMeta, LinkMeta, ToggleMeta, ProgressMeta];
|
||||||
|
for (const meta of metas) {
|
||||||
|
expect(meta.name).toBeTruthy();
|
||||||
|
expect(meta.description).toBeTruthy();
|
||||||
|
expect(meta.parts.length).toBeGreaterThan(0);
|
||||||
|
expect(meta.requiredParts.length).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user