diff --git a/ipa/dev/component-fixtures.mdx b/ipa/dev/component-fixtures.mdx index 5c0c6bd..adee63a 100644 --- a/ipa/dev/component-fixtures.mdx +++ b/ipa/dev/component-fixtures.mdx @@ -119,6 +119,49 @@ name: list-resources +## `` + +`` is the short prose explanation inside `` and +`` blocks — why the pattern is correct or incorrect, the +principle rather than a restatement of the guideline. It renders beneath the +code block under the example's tinted hairline, with an inline "Why:" lead-in in +the verdict color. + +### Inside a correct example + + + +```yaml +PATCH /clusters/{clusterId} +Content-Type: application/merge-patch+json +``` + + + Merge-patch is simpler for clients — send only the fields to change. + + + + +### Inside an incorrect example, long-form + + + +```yaml +components: + schemas: + Cluster: + properties: + _id: { type: string } + __v: { type: integer } +``` + + + Exposing `_id` and `__v` leaks MongoDB document internals into the API + surface, coupling clients to the storage layer. + + + + ## `` `` renders the manual evaluation steps of a guideline as a numbered diff --git a/src/components/ipa/Example/Reason.module.css b/src/components/ipa/Example/Reason.module.css new file mode 100644 index 0000000..d8abdfb --- /dev/null +++ b/src/components/ipa/Example/Reason.module.css @@ -0,0 +1,31 @@ +/* The --ex-* tokens are defined on the Example variant root + (Example.module.css) and reach these rules by cascade. */ +.reason { + margin-top: 0.75rem; + padding-top: 0.6rem; + border-top: 1px solid var(--ex-border); + font-size: 0.85rem; + line-height: 1.6; + color: var(--ifm-font-color-base); +} + +.lead { + font-weight: 700; + color: var(--ex-header-color); +} + +.reason code { + font-size: 88%; +} + +/* MDX wraps the prose in

: the first paragraph flows inline after the + "Why:" lead, subsequent paragraphs break below. */ +.reason p { + display: inline; + margin: 0; +} + +.reason p + p { + display: block; + margin-top: 0.5rem; +} diff --git a/src/components/ipa/Example/Reason.test.tsx b/src/components/ipa/Example/Reason.test.tsx new file mode 100644 index 0000000..0aa4e27 --- /dev/null +++ b/src/components/ipa/Example/Reason.test.tsx @@ -0,0 +1,58 @@ +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; + +import { Example } from "./index"; + +describe("", () => { + it("renders the explanation prose", () => { + render( + Merge-patch is simpler for clients., + ); + + expect( + screen.getByText(/merge-patch is simpler for clients/i), + ).toBeInTheDocument(); + }); + + it("renders a 'Why:' lead-in", () => { + render(Some explanation.); + + expect(screen.getByText("Why:")).toBeInTheDocument(); + }); + + it("accepts paragraph children without invalid DOM nesting", () => { + // MDX wraps indented multi-line children in

, so the component's + // container must not itself be a

(React warns via console.error). + const error = vi.spyOn(console, "error").mockImplementation(() => {}); + + render( + +

First sentence of the reason.

+
, + ); + + expect( + screen.getByText(/first sentence of the reason/i), + ).toBeInTheDocument(); + expect(error).not.toHaveBeenCalled(); + + error.mockRestore(); + }); + + it("renders inside an example beneath its code block", () => { + render( + +
+          name: list-resources
+        
+ The name field is required. +
, + ); + const code = screen.getByText("name: list-resources"); + const reason = screen.getByText(/the name field is required/i); + + expect( + code.compareDocumentPosition(reason) & Node.DOCUMENT_POSITION_FOLLOWING, + ).toBeTruthy(); + }); +}); diff --git a/src/components/ipa/Example/Reason.tsx b/src/components/ipa/Example/Reason.tsx new file mode 100644 index 0000000..d6ef699 --- /dev/null +++ b/src/components/ipa/Example/Reason.tsx @@ -0,0 +1,14 @@ +import { type ReactElement, type ReactNode } from "react"; +import styles from "./Reason.module.css"; + +interface ReasonProps { + children: ReactNode; +} + +export function Reason({ children }: ReasonProps): ReactElement { + return ( +
+ Why: {children} +
+ ); +} diff --git a/src/components/ipa/Example/index.tsx b/src/components/ipa/Example/index.tsx index 30e9a10..b673621 100644 --- a/src/components/ipa/Example/index.tsx +++ b/src/components/ipa/Example/index.tsx @@ -1,6 +1,7 @@ import { type ReactElement, type ReactNode } from "react"; import { Accordion } from "@site/src/components/ui"; import { type ExampleType } from "./types"; +import { Reason } from "./Reason"; import styles from "./Example.module.css"; import clsx from "clsx"; @@ -47,4 +48,5 @@ IncorrectExample.displayName = "Incorrect"; export const Example = Object.assign(ExampleBase, { Correct: CorrectExample, Incorrect: IncorrectExample, + Reason: Reason, });