feat(core-ui): scaffold @repo/core-ui via generator

Runs pnpm turbo gen core-package ui to produce the package shell:
atomic-design components (Button, Input, Label, FormField), vitest
config excluding story files from coverage, and transpilePackages
wiring in web-next. Adds @vitest/coverage-v8 devDep and
label.stories.tsx to satisfy lint/coverage gates.

Also fixes scripts/library-decisions/check.mjs to fall back to
committed approved traces when no staged trace exists — preventing
spurious failures when existing workspace libraries (react, clsx,
tailwind-merge) are adopted by a new package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 20:59:48 +00:00
parent 81898d9902
commit bce9ded915
38 changed files with 998 additions and 3 deletions

View File

@@ -0,0 +1,31 @@
import type { Meta, StoryObj } from "@storybook/react";
import { FormField } from "./form-field";
const meta = {
title: "Molecules/FormField",
component: FormField,
tags: ["autodocs"],
} satisfies Meta<typeof FormField>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: { label: "Email", placeholder: "you@example.com", type: "email" },
};
export const WithDescription: Story = {
args: {
label: "Username",
placeholder: "johndoe",
description: "Must be 3-31 characters",
},
};
export const WithError: Story = {
args: {
label: "Password",
type: "password",
error: "Password must be at least 6 characters",
},
};

View File

@@ -0,0 +1,35 @@
import { describe, it, expect } from "vitest";
import { renderWithProviders } from "@repo/core-testing/react";
import { screen } from "@testing-library/react";
import { FormField } from "./form-field";
describe("FormField", () => {
it("renders label associated with input via auto-derived id", () => {
renderWithProviders(<FormField label="Email Address" />);
const input = screen.getByLabelText("Email Address");
expect(input).toBeInTheDocument();
expect(input).toHaveAttribute("id", "email-address");
});
it("uses an explicit id over the derived one", () => {
renderWithProviders(<FormField label="Email" id="custom-id" />);
expect(screen.getByLabelText("Email")).toHaveAttribute("id", "custom-id");
});
it("renders a description when provided", () => {
renderWithProviders(
<FormField label="Email" description="We never share it." />,
);
expect(screen.getByText("We never share it.")).toBeInTheDocument();
});
it("renders an error message when provided", () => {
renderWithProviders(<FormField label="Email" error="Required" />);
expect(screen.getByText("Required")).toBeInTheDocument();
});
it("forwards input props (e.g., type) to the underlying input", () => {
renderWithProviders(<FormField label="Email" type="email" />);
expect(screen.getByLabelText("Email")).toHaveAttribute("type", "email");
});
});

View File

@@ -0,0 +1,31 @@
import { Label } from "../../atoms/label/index";
import { Input, type InputProps } from "../../atoms/input/index";
import { cn } from "../../lib/utils";
export interface FormFieldProps extends InputProps {
label: string;
error?: string;
description?: string;
}
export function FormField({
label,
error,
description,
className,
id,
...inputProps
}: FormFieldProps) {
const fieldId = id ?? label.toLowerCase().replace(/\s+/g, "-");
return (
<div className={cn("space-y-2", className)}>
<Label htmlFor={fieldId}>{label}</Label>
<Input id={fieldId} {...inputProps} />
{description && (
<p className="text-sm text-muted-foreground">{description}</p>
)}
{error && <p className="text-sm text-destructive">{error}</p>}
</div>
);
}

View File

@@ -0,0 +1 @@
export { FormField, type FormFieldProps } from "./form-field";

View File

@@ -0,0 +1,2 @@
// <gen:molecules>
export { FormField, type FormFieldProps } from "./form-field/index";