@sproutsocial/seeds-react-narrative-kit 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.turbo/turbo-build.log +11 -11
  2. package/CHANGELOG.md +19 -0
  3. package/dist/esm/index.js +119 -91
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/index.d.mts +110 -11
  6. package/dist/index.d.ts +110 -11
  7. package/dist/index.js +123 -93
  8. package/dist/index.js.map +1 -1
  9. package/dist/metric-highlight.css +17 -27
  10. package/dist/narrative-divider.css +27 -0
  11. package/dist/narrative-headline.css +7 -0
  12. package/dist/narrative-summary.css +126 -0
  13. package/package.json +7 -3
  14. package/src/EyebrowToken/EyebrowToken.tsx +1 -5
  15. package/src/MetricHighlight/MetricHighlight.stories.tsx +37 -14
  16. package/src/MetricHighlight/MetricHighlight.tsx +19 -99
  17. package/src/MetricHighlight/MetricHighlightTypes.ts +6 -9
  18. package/src/MetricHighlight/__tests__/MetricHighlight.test.tsx +13 -48
  19. package/src/NarrativeDivider/NarrativeDivider.stories.tsx +33 -0
  20. package/src/NarrativeDivider/NarrativeDivider.tsx +30 -0
  21. package/src/NarrativeDivider/NarrativeDividerTypes.ts +4 -0
  22. package/src/NarrativeDivider/__tests__/NarrativeDivider.test.tsx +35 -0
  23. package/src/NarrativeDivider/index.ts +2 -0
  24. package/src/NarrativeHeadline/NarrativeHeadline.tsx +21 -12
  25. package/src/NarrativeHeadline/NarrativeHeadlineRoll.tsx +4 -1
  26. package/src/NarrativeHeadline/NarrativeHeadlineTypes.ts +9 -0
  27. package/src/NarrativeHeadline/__tests__/NarrativeHeadline.test.tsx +2 -7
  28. package/src/NarrativeHeadline/index.ts +1 -0
  29. package/src/NarrativeSummary/NarrativeSummary.stories.tsx +79 -0
  30. package/src/NarrativeSummary/NarrativeSummary.tsx +98 -0
  31. package/src/NarrativeSummary/NarrativeSummaryTypes.ts +59 -0
  32. package/src/NarrativeSummary/__tests__/NarrativeSummary.test.tsx +99 -0
  33. package/src/NarrativeSummary/index.ts +5 -0
  34. package/src/Playground/Playground.stories.tsx +86 -36
  35. package/src/PullQuote/PullQuote.stories.tsx +3 -1
  36. package/src/PullQuote/PullQuoteTypes.ts +1 -2
  37. package/src/index.ts +11 -0
  38. package/src/metric-highlight.css +17 -27
  39. package/src/narrative-divider.css +27 -0
  40. package/src/narrative-headline.css +7 -0
  41. package/src/narrative-summary.css +126 -0
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import NarrativeDivider from "./NarrativeDivider";
4
+ import "../narrative-divider.css";
5
+
6
+ const meta = {
7
+ title: "Narrative Kit/NarrativeDivider",
8
+ component: NarrativeDivider,
9
+ decorators: [
10
+ (Story) => (
11
+ <div style={{ width: 480 }}>
12
+ <Story />
13
+ </div>
14
+ ),
15
+ ],
16
+ } satisfies Meta<typeof NarrativeDivider>;
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof meta>;
20
+
21
+ /** A full-width 1px rule spanning its container. */
22
+ export const Default: Story = {};
23
+
24
+ /** Sitting between two blocks of content, as it would in a brief. */
25
+ export const BetweenSections: Story = {
26
+ render: () => (
27
+ <div style={{ width: 480 }}>
28
+ <p>Insight metrics</p>
29
+ <NarrativeDivider />
30
+ <p>Headline and summary</p>
31
+ </div>
32
+ ),
33
+ };
@@ -0,0 +1,30 @@
1
+ import * as React from "react";
2
+ import { cn } from "../_internal/cn";
3
+ import type { TypeNarrativeDividerProps } from "./NarrativeDividerTypes";
4
+
5
+ /**
6
+ * NarrativeDivider — a full-width, 1px horizontal rule that separates sections
7
+ * within a narrative brief (e.g. a row of metrics from the headline/summary
8
+ * block below it).
9
+ *
10
+ * Renders a semantic <hr> stripped of its user-agent margins and borders, then
11
+ * draws a single 1px line with the container-border-base token (#dee1e1, which
12
+ * also tracks dark mode). Styled via narrative-divider.css (Seeds CSS custom
13
+ * properties); consumers import the classes through
14
+ * @sproutsocial/racine/css/components. Overrides come via standard className /
15
+ * style.
16
+ */
17
+ const NarrativeDivider = React.forwardRef<
18
+ HTMLHRElement,
19
+ TypeNarrativeDividerProps
20
+ >(({ className, ...props }, ref) => (
21
+ <hr
22
+ ref={ref}
23
+ className={cn("seeds-narrative-divider", className)}
24
+ {...props}
25
+ />
26
+ ));
27
+
28
+ NarrativeDivider.displayName = "NarrativeDivider";
29
+
30
+ export default NarrativeDivider;
@@ -0,0 +1,4 @@
1
+ import type * as React from "react";
2
+
3
+ /** NarrativeDivider takes no extra props — just standard <hr> attributes. */
4
+ export type TypeNarrativeDividerProps = React.HTMLAttributes<HTMLHRElement>;
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import { render, screen } from "@sproutsocial/seeds-react-testing-library";
3
+ import NarrativeDivider from "../NarrativeDivider";
4
+
5
+ describe("NarrativeDivider", () => {
6
+ it("renders a horizontal rule with the base class", () => {
7
+ const { container } = render(<NarrativeDivider />);
8
+
9
+ const root = container.querySelector(".seeds-narrative-divider");
10
+ expect(root).toBeInTheDocument();
11
+ expect(root?.tagName).toBe("HR");
12
+ });
13
+
14
+ it("merges a consumer className with the base class", () => {
15
+ const { container } = render(<NarrativeDivider className="custom" />);
16
+
17
+ const root = container.querySelector(".seeds-narrative-divider");
18
+ expect(root).toBeInTheDocument();
19
+ expect(root).toHaveClass("custom");
20
+ });
21
+
22
+ it("forwards extra props to the underlying element", () => {
23
+ render(<NarrativeDivider data-testid="divider" />);
24
+
25
+ expect(screen.getByTestId("divider")).toBeInTheDocument();
26
+ });
27
+
28
+ it("forwards its ref to the underlying hr element", () => {
29
+ const ref = React.createRef<HTMLHRElement>();
30
+ render(<NarrativeDivider ref={ref} />);
31
+
32
+ expect(ref.current).toBeInstanceOf(HTMLHRElement);
33
+ expect(ref.current?.tagName).toBe("HR");
34
+ });
35
+ });
@@ -0,0 +1,2 @@
1
+ export { default as NarrativeDivider } from "./NarrativeDivider";
2
+ export type { TypeNarrativeDividerProps } from "./NarrativeDividerTypes";
@@ -17,19 +17,28 @@ import type { TypeNarrativeHeadlineProps } from "./NarrativeHeadlineTypes";
17
17
  const NarrativeHeadline = React.forwardRef<
18
18
  HTMLHeadingElement,
19
19
  TypeNarrativeHeadlineProps
20
- >(({ children, headingLevel = 2, className, ...props }, ref) => {
21
- const Heading = `h${headingLevel}` as const;
20
+ >(
21
+ (
22
+ { children, headingLevel = 2, size = "default", className, ...props },
23
+ ref
24
+ ) => {
25
+ const Heading = `h${headingLevel}` as const;
22
26
 
23
- return (
24
- <Heading
25
- ref={ref}
26
- className={cn("seeds-narrative-headline", className)}
27
- {...props}
28
- >
29
- {children}
30
- </Heading>
31
- );
32
- });
27
+ return (
28
+ <Heading
29
+ ref={ref}
30
+ className={cn(
31
+ "seeds-narrative-headline",
32
+ { "seeds-narrative-headline-small": size === "small" },
33
+ className
34
+ )}
35
+ {...props}
36
+ >
37
+ {children}
38
+ </Heading>
39
+ );
40
+ }
41
+ );
33
42
 
34
43
  NarrativeHeadline.displayName = "NarrativeHeadline";
35
44
 
@@ -74,7 +74,10 @@ const NarrativeHeadlineRoll = React.forwardRef<
74
74
  >
75
75
  {/* Sizer keeps the container width matched to the active word — without
76
76
  it the absolutely-positioned words would collapse the box. */}
77
- <span className="seeds-narrative-headline-roll-sizer" aria-hidden="true">
77
+ <span
78
+ className="seeds-narrative-headline-roll-sizer"
79
+ aria-hidden="true"
80
+ >
78
81
  {words[i]}
79
82
  </span>
80
83
  {words.map((w, idx) => (
@@ -3,6 +3,9 @@ import type * as React from "react";
3
3
  /** Heading level the headline text renders as. */
4
4
  export type TypeNarrativeHeadlineLevel = 1 | 2 | 3 | 4 | 5 | 6;
5
5
 
6
+ /** Visual size of the headline. Independent of the semantic heading level. */
7
+ export type TypeNarrativeHeadlineSize = "default" | "small";
8
+
6
9
  export interface TypeNarrativeHeadlineProps
7
10
  extends React.HTMLAttributes<HTMLHeadingElement> {
8
11
  /**
@@ -17,6 +20,12 @@ export interface TypeNarrativeHeadlineProps
17
20
  * @default 2
18
21
  */
19
22
  headingLevel?: TypeNarrativeHeadlineLevel;
23
+ /**
24
+ * Visual size. `"default"` is the display size (32px / 40px); `"small"` is the
25
+ * compact size (24px / 32px) used inside denser layouts like NarrativeSummary.
26
+ * @default "default"
27
+ */
28
+ size?: TypeNarrativeHeadlineSize;
20
29
  }
21
30
 
22
31
  export interface TypeNarrativeHeadlineHighlightProps
@@ -12,9 +12,7 @@ describe("NarrativeHeadline", () => {
12
12
  });
13
13
 
14
14
  it("renders the headline as an h2 by default and honors headingLevel", () => {
15
- const { rerender } = render(
16
- <NarrativeHeadline>Title</NarrativeHeadline>
17
- );
15
+ const { rerender } = render(<NarrativeHeadline>Title</NarrativeHeadline>);
18
16
  expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent(
19
17
  "Title"
20
18
  );
@@ -101,10 +99,7 @@ describe("NarrativeHeadlineRoll", () => {
101
99
  jest.useFakeTimers();
102
100
  try {
103
101
  const { container } = render(
104
- <NarrativeHeadlineRoll
105
- words={["first", "second"]}
106
- intervalMs={1000}
107
- />
102
+ <NarrativeHeadlineRoll words={["first", "second"]} intervalMs={1000} />
108
103
  );
109
104
 
110
105
  const wordsBefore = container.querySelectorAll(
@@ -4,6 +4,7 @@ export { default as NarrativeHeadlineRoll } from "./NarrativeHeadlineRoll";
4
4
  export type {
5
5
  TypeNarrativeHeadlineProps,
6
6
  TypeNarrativeHeadlineLevel,
7
+ TypeNarrativeHeadlineSize,
7
8
  TypeNarrativeHeadlineHighlightProps,
8
9
  TypeNarrativeHeadlineRollProps,
9
10
  } from "./NarrativeHeadlineTypes";
@@ -0,0 +1,79 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import Button from "@sproutsocial/seeds-react-button";
4
+ import NarrativeSummary from "./NarrativeSummary";
5
+ import "../narrative-summary.css";
6
+ import "../narrative-headline.css";
7
+ import "../eyebrow-token.css";
8
+ import "@sproutsocial/seeds-react-button/dist/button.css";
9
+
10
+ /**
11
+ * NarrativeSummary pairs a lead column (eyebrow + headline + optional action)
12
+ * with a Summary section and a Key themes list. It renders inner content only,
13
+ * meant to be dropped into a `NarrativeContainer` in production (see the
14
+ * Playground story for that composition).
15
+ *
16
+ * The layout is a container-query-driven CSS grid, so it responds to the width
17
+ * it is given rather than the viewport.
18
+ */
19
+ const meta = {
20
+ title: "Narrative Kit/NarrativeSummary",
21
+ component: NarrativeSummary,
22
+ args: {
23
+ eyebrow: "June 10th",
24
+ headline: "Headline goes here and wraps to second line",
25
+ summary:
26
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec odio leo, gravida vitae sodales vitae, tempor eget ligula. Vestibulum nec enim eget mi placerat finibus. Donec tempor tincidunt elit. Praesent pretium libero vitae arcu blandit pellentesque. Aenean fermentum, nisi eu blandit placerat, ligula ligula euismod ante, id semper nibh diam ac ipsum.",
27
+ keyThemes: [
28
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
29
+ "Donec odio leo, gravida vitae sodales vitae, tempor eget ligula.",
30
+ "Vestibulum nec enim eget mi placerat finibus.",
31
+ "Donec tempor tincidunt elit. Praesent pretium libero vitae arcu blandit pellentesque.",
32
+ ],
33
+ // No system props → the Button renders its Tailwind path.
34
+ action: <Button appearance="primary">Action</Button>,
35
+ },
36
+ // Constrain the width so the grid has a realistic amount of room to lay out.
37
+ decorators: [
38
+ (Story) => (
39
+ <div className="max-w-256">
40
+ <Story />
41
+ </div>
42
+ ),
43
+ ],
44
+ } satisfies Meta<typeof NarrativeSummary>;
45
+
46
+ export default meta;
47
+ type Story = StoryObj<typeof meta>;
48
+
49
+ /** Lead | Summary | Key themes — three side-by-side columns. */
50
+ export const ThreeColumn: Story = {};
51
+
52
+ /** A 1/3 | 2/3 split: the lead column beside a stacked Summary + Key themes. */
53
+ export const TwoColumn: Story = {
54
+ args: {
55
+ layout: "two-column",
56
+ },
57
+ };
58
+
59
+ /** The action is optional — omit it and the lead column drops the button. */
60
+ export const WithoutAction: Story = {
61
+ args: {
62
+ action: undefined,
63
+ },
64
+ };
65
+
66
+ /**
67
+ * Responsiveness is container-driven. In this narrow container the grid
68
+ * collapses to a single stacked column; widen the container (or view the
69
+ * ThreeColumn story) to see it expand. Resize the canvas to watch it reflow.
70
+ */
71
+ export const Responsive: Story = {
72
+ decorators: [
73
+ (Story) => (
74
+ <div className="max-w-96">
75
+ <Story />
76
+ </div>
77
+ ),
78
+ ],
79
+ };
@@ -0,0 +1,98 @@
1
+ import * as React from "react";
2
+ import { cn } from "../_internal/cn";
3
+ import EyebrowToken from "../EyebrowToken/EyebrowToken";
4
+ import NarrativeHeadline from "../NarrativeHeadline/NarrativeHeadline";
5
+ import type { TypeNarrativeSummaryProps } from "./NarrativeSummaryTypes";
6
+
7
+ /**
8
+ * NarrativeSummary — a composed narrative block that pairs a lead column
9
+ * (eyebrow + headline + optional action) with a Summary section and a Key
10
+ * themes list.
11
+ *
12
+ * Renders inner content only — it carries no surface, shadow, or accent of its
13
+ * own. Drop it into a `NarrativeContainer` (see the stories / playground).
14
+ *
15
+ * Layout is CSS Grid driven by container queries, so the columns collapse based
16
+ * on the width the component is actually given, not the viewport:
17
+ * - `"three-column"` → lead | Summary | Key themes.
18
+ * - `"two-column"` → a 1/3 | 2/3 split with Summary and Key themes stacked in
19
+ * the wider column.
20
+ *
21
+ * Styled via `narrative-summary.css` (Seeds CSS custom properties); consumers
22
+ * import the classes through `@sproutsocial/racine/css/components`. Overrides
23
+ * come via standard `className` / `style`.
24
+ */
25
+ const NarrativeSummary = React.forwardRef<
26
+ HTMLDivElement,
27
+ TypeNarrativeSummaryProps
28
+ >(
29
+ (
30
+ {
31
+ eyebrow,
32
+ headline,
33
+ headingLevel = 2,
34
+ action,
35
+ summary,
36
+ summaryLabel = "Summary",
37
+ keyThemes,
38
+ keyThemesLabel = "Key themes",
39
+ layout = "three-column",
40
+ className,
41
+ ...props
42
+ },
43
+ ref
44
+ ) => {
45
+ const hasKeyThemes = keyThemes != null && keyThemes.length > 0;
46
+
47
+ return (
48
+ <div
49
+ ref={ref}
50
+ className={cn("seeds-narrative-summary", className)}
51
+ {...props}
52
+ >
53
+ <div
54
+ className={cn("seeds-narrative-summary-grid", {
55
+ "seeds-narrative-summary-two-column": layout === "two-column",
56
+ })}
57
+ >
58
+ <div className="seeds-narrative-summary-lead">
59
+ {eyebrow != null && <EyebrowToken>{eyebrow}</EyebrowToken>}
60
+ <div className="seeds-narrative-summary-headline">
61
+ <NarrativeHeadline size="small" headingLevel={headingLevel}>
62
+ {headline}
63
+ </NarrativeHeadline>
64
+ </div>
65
+ {action != null && (
66
+ <div className="seeds-narrative-summary-action">{action}</div>
67
+ )}
68
+ </div>
69
+
70
+ <div className="seeds-narrative-summary-content">
71
+ {summary != null && (
72
+ <section className="seeds-narrative-summary-section">
73
+ <p className="seeds-narrative-summary-label">{summaryLabel}</p>
74
+ <p className="seeds-narrative-summary-text">{summary}</p>
75
+ </section>
76
+ )}
77
+ {hasKeyThemes && (
78
+ <section className="seeds-narrative-summary-section">
79
+ <p className="seeds-narrative-summary-label">
80
+ {keyThemesLabel}
81
+ </p>
82
+ <ul className="seeds-narrative-summary-list">
83
+ {keyThemes.map((theme, i) => (
84
+ <li key={i}>{theme}</li>
85
+ ))}
86
+ </ul>
87
+ </section>
88
+ )}
89
+ </div>
90
+ </div>
91
+ </div>
92
+ );
93
+ }
94
+ );
95
+
96
+ NarrativeSummary.displayName = "NarrativeSummary";
97
+
98
+ export default NarrativeSummary;
@@ -0,0 +1,59 @@
1
+ import type * as React from "react";
2
+ import type { TypeNarrativeHeadlineLevel } from "../NarrativeHeadline/NarrativeHeadlineTypes";
3
+
4
+ /**
5
+ * Column arrangement.
6
+ * - `"three-column"`: lead | Summary | Key themes, three side-by-side columns.
7
+ * - `"two-column"`: a 1/3 | 2/3 split — lead on the left, with Summary and Key
8
+ * themes stacked together in the wider right column.
9
+ */
10
+ export type TypeNarrativeSummaryLayout = "three-column" | "two-column";
11
+
12
+ export interface TypeNarrativeSummaryProps
13
+ extends React.HTMLAttributes<HTMLDivElement> {
14
+ /**
15
+ * Kicker shown in an `EyebrowToken` above the headline (e.g. a date). Omit to
16
+ * hide the eyebrow entirely.
17
+ */
18
+ eyebrow?: React.ReactNode;
19
+ /** Headline text — rendered via `NarrativeHeadline` at `size="small"`. */
20
+ headline: React.ReactNode;
21
+ /**
22
+ * Heading element the headline renders as, for document outline /
23
+ * accessibility.
24
+ * @default 2
25
+ */
26
+ headingLevel?: TypeNarrativeHeadlineLevel;
27
+ /**
28
+ * Optional action, typically a `<Button>`. Rendered below the headline only
29
+ * when provided. Pass a Button without system props so it renders the Tailwind
30
+ * path (e.g. `<Button appearance="primary">Action</Button>`).
31
+ *
32
+ * Note: in the MVP this will most often be left unset (null) — the AC is
33
+ * poorly defined at this time and needs more definition before the data model
34
+ * will properly support it.
35
+ */
36
+ action?: React.ReactNode;
37
+ /** Summary body content. The section is omitted when this is empty. */
38
+ summary?: React.ReactNode;
39
+ /**
40
+ * Label above the summary.
41
+ * @default "Summary"
42
+ */
43
+ summaryLabel?: React.ReactNode;
44
+ /**
45
+ * Key themes, rendered as a bulleted list. The section is omitted when this
46
+ * is empty.
47
+ */
48
+ keyThemes?: string[];
49
+ /**
50
+ * Label above the key themes.
51
+ * @default "Key themes"
52
+ */
53
+ keyThemesLabel?: React.ReactNode;
54
+ /**
55
+ * Column arrangement. `"two-column"` is a 1/3 | 2/3 split.
56
+ * @default "three-column"
57
+ */
58
+ layout?: TypeNarrativeSummaryLayout;
59
+ }
@@ -0,0 +1,99 @@
1
+ import React from "react";
2
+ import { render, screen } from "@sproutsocial/seeds-react-testing-library";
3
+ import NarrativeSummary from "../NarrativeSummary";
4
+
5
+ const baseProps = {
6
+ headline: "Headline goes here",
7
+ summary: "A short summary.",
8
+ keyThemes: ["Theme one", "Theme two"],
9
+ };
10
+
11
+ describe("NarrativeSummary", () => {
12
+ it("renders the headline, eyebrow, summary, and key themes", () => {
13
+ render(<NarrativeSummary {...baseProps} eyebrow="June 10th" />);
14
+
15
+ expect(screen.getByText("June 10th")).toBeInTheDocument();
16
+ expect(
17
+ screen.getByRole("heading", { name: "Headline goes here" })
18
+ ).toBeInTheDocument();
19
+ expect(screen.getByText("A short summary.")).toBeInTheDocument();
20
+ expect(screen.getByText("Theme one")).toBeInTheDocument();
21
+ expect(screen.getByText("Theme two")).toBeInTheDocument();
22
+ });
23
+
24
+ it("renders the headline at the small size", () => {
25
+ const { container } = render(<NarrativeSummary {...baseProps} />);
26
+
27
+ expect(
28
+ container.querySelector(".seeds-narrative-headline-small")
29
+ ).toBeInTheDocument();
30
+ });
31
+
32
+ it("uses the default section labels and allows overrides", () => {
33
+ const { rerender } = render(<NarrativeSummary {...baseProps} />);
34
+ expect(screen.getByText("Summary")).toBeInTheDocument();
35
+ expect(screen.getByText("Key themes")).toBeInTheDocument();
36
+
37
+ rerender(
38
+ <NarrativeSummary
39
+ {...baseProps}
40
+ summaryLabel="Overview"
41
+ keyThemesLabel="Highlights"
42
+ />
43
+ );
44
+ expect(screen.getByText("Overview")).toBeInTheDocument();
45
+ expect(screen.getByText("Highlights")).toBeInTheDocument();
46
+ });
47
+
48
+ it("renders the key themes as list items", () => {
49
+ render(<NarrativeSummary {...baseProps} />);
50
+
51
+ expect(screen.getAllByRole("listitem")).toHaveLength(2);
52
+ });
53
+
54
+ it("renders the action only when provided", () => {
55
+ const { rerender } = render(<NarrativeSummary {...baseProps} />);
56
+ expect(
57
+ screen.queryByRole("button", { name: "Action" })
58
+ ).not.toBeInTheDocument();
59
+
60
+ rerender(
61
+ <NarrativeSummary {...baseProps} action={<button>Action</button>} />
62
+ );
63
+ expect(screen.getByRole("button", { name: "Action" })).toBeInTheDocument();
64
+ });
65
+
66
+ it("omits a section when its content is empty", () => {
67
+ render(<NarrativeSummary headline="Only summary" summary="Just this." />);
68
+
69
+ expect(screen.getByText("Summary")).toBeInTheDocument();
70
+ expect(screen.queryByText("Key themes")).not.toBeInTheDocument();
71
+ });
72
+
73
+ it("applies the two-column modifier class", () => {
74
+ const { container } = render(
75
+ <NarrativeSummary {...baseProps} layout="two-column" />
76
+ );
77
+
78
+ expect(
79
+ container.querySelector(".seeds-narrative-summary-two-column")
80
+ ).toBeInTheDocument();
81
+ });
82
+
83
+ it("merges a consumer className with the base class", () => {
84
+ const { container } = render(
85
+ <NarrativeSummary {...baseProps} className="custom" />
86
+ );
87
+
88
+ const root = container.querySelector(".seeds-narrative-summary");
89
+ expect(root).toBeInTheDocument();
90
+ expect(root).toHaveClass("custom");
91
+ });
92
+
93
+ it("forwards its ref to the underlying div element", () => {
94
+ const ref = React.createRef<HTMLDivElement>();
95
+ render(<NarrativeSummary {...baseProps} ref={ref} />);
96
+
97
+ expect(ref.current).toBeInstanceOf(HTMLDivElement);
98
+ });
99
+ });
@@ -0,0 +1,5 @@
1
+ export { default as NarrativeSummary } from "./NarrativeSummary";
2
+ export type {
3
+ TypeNarrativeSummaryProps,
4
+ TypeNarrativeSummaryLayout,
5
+ } from "./NarrativeSummaryTypes";