diff --git a/changes/45227-long-policy-res b/changes/45227-long-policy-res new file mode 100644 index 0000000000..b0c36edd25 --- /dev/null +++ b/changes/45227-long-policy-res @@ -0,0 +1 @@ +* Long policy resolution text now wraps on the policy details page instead of being truncated. diff --git a/frontend/components/DataSet/DataSet.stories.tsx b/frontend/components/DataSet/DataSet.stories.tsx index 40dcb565bb..6571cc7251 100644 --- a/frontend/components/DataSet/DataSet.stories.tsx +++ b/frontend/components/DataSet/DataSet.stories.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { Meta, StoryObj } from "@storybook/react"; import DataSet from "./DataSet"; @@ -22,3 +23,19 @@ export const HorizontalOrientation: Story = { orientation: "horizontal", }, }; + +// Multiline values wrap instead of truncating with an ellipsis. Use for +// free-form prose like "Resolve" or "Description". +export const Multiline: Story = { + args: { + title: "Resolve", + value: + "Re-enable FileVault on the device. Open System Settings, navigate to Privacy & Security, and turn on FileVault. Store the recovery key in a safe place.", + multiline: true, + }, + decorators: [ + (Story) => ( +
{Story()}
+ ), + ], +}; diff --git a/frontend/components/DataSet/DataSet.tests.tsx b/frontend/components/DataSet/DataSet.tests.tsx new file mode 100644 index 0000000000..80fe824e90 --- /dev/null +++ b/frontend/components/DataSet/DataSet.tests.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; + +import DataSet from "./DataSet"; + +describe("DataSet", () => { + it("renders title and value", () => { + render(); + expect(screen.getByText("Author")).toBeInTheDocument(); + expect(screen.getByText("Alice")).toBeInTheDocument(); + }); + + it("uses vertical orientation by default (no horizontal class, no colon)", () => { + const { container } = render(); + expect(container.querySelector(".data-set__horizontal")).toBeNull(); + expect(container.querySelector("dt")?.textContent).toBe("Author"); + }); + + it("applies horizontal orientation class and appends a colon to the title", () => { + const { container } = render( + + ); + expect( + container.querySelector(".data-set__horizontal") + ).toBeInTheDocument(); + expect(container.querySelector("dt")?.textContent).toBe("Author:"); + }); + + it("applies the text-only modifier when textOnly is set", () => { + const { container } = render( + + ); + expect(container.querySelector(".data-set--text-only")).toBeInTheDocument(); + }); + + it("applies the multiline modifier when multiline is set", () => { + const { container } = render( + + ); + expect(container.querySelector(".data-set--multiline")).toBeInTheDocument(); + }); + + it("does not apply the multiline modifier by default", () => { + const { container } = render(); + expect(container.querySelector(".data-set--multiline")).toBeNull(); + }); + + it("merges a custom className alongside the base class", () => { + const { container } = render( + + ); + const root = container.firstChild as HTMLElement; + expect(root).toHaveClass("data-set"); + expect(root).toHaveClass("custom-class"); + }); +}); diff --git a/frontend/components/DataSet/DataSet.tsx b/frontend/components/DataSet/DataSet.tsx index ddc952c637..60279b0f39 100644 --- a/frontend/components/DataSet/DataSet.tsx +++ b/frontend/components/DataSet/DataSet.tsx @@ -13,6 +13,12 @@ interface IDataSetProps { * baseline. Do NOT use when the value contains icons, buttons, or status * indicators that need vertical centering. */ textOnly?: boolean; + /** When true, the value wraps onto multiple lines instead of truncating + * with an ellipsis. Use for free-form prose like "Resolve" or + * "Description" where the full text needs to be readable. Default behavior + * (single-line truncation with ellipsis) assumes the consumer wraps the + * value in a tooltip-on-truncate helper. */ + multiline?: boolean; className?: string; } @@ -21,11 +27,13 @@ const DataSet = ({ value, orientation = "vertical", textOnly = false, + multiline = false, className, }: IDataSetProps) => { const classNames = classnames(baseClass, className, { [`${baseClass}__horizontal`]: orientation === "horizontal", [`${baseClass}--text-only`]: textOnly, + [`${baseClass}--multiline`]: multiline, }); return ( diff --git a/frontend/components/DataSet/_styles.scss b/frontend/components/DataSet/_styles.scss index 0ee63f3e41..d066c4acee 100644 --- a/frontend/components/DataSet/_styles.scss +++ b/frontend/components/DataSet/_styles.scss @@ -23,6 +23,12 @@ align-items: baseline; } + &--multiline dd { + white-space: pre-wrap; + overflow: visible; + overflow-wrap: anywhere; + } + &__horizontal { flex-direction: row; } diff --git a/frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx b/frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx index 9860b114de..3dafe7c647 100644 --- a/frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx +++ b/frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx @@ -426,6 +426,7 @@ const PolicyDetailsPage = ({ className={`${baseClass}__resolve`} title="Resolve" value={lastEditedQueryResolution} + multiline /> )} {renderAuthor()}