@sproutsocial/seeds-react-narrative-kit 0.2.0 → 0.4.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 (65) hide show
  1. package/.turbo/turbo-build.log +11 -11
  2. package/CHANGELOG.md +46 -0
  3. package/dist/ai-container.css +39 -0
  4. package/dist/esm/index.js +217 -91
  5. package/dist/esm/index.js.map +1 -1
  6. package/dist/index.d.mts +232 -14
  7. package/dist/index.d.ts +232 -14
  8. package/dist/index.js +224 -93
  9. package/dist/index.js.map +1 -1
  10. package/dist/metric-highlight.css +17 -27
  11. package/dist/narrative-data-callout.css +103 -0
  12. package/dist/narrative-divider.css +27 -0
  13. package/dist/narrative-headline.css +7 -0
  14. package/dist/narrative-ordered-list.css +112 -0
  15. package/dist/narrative-summary.css +126 -0
  16. package/dist/pull-quote.css +4 -25
  17. package/package.json +12 -5
  18. package/src/AIContainer/AIContainer.stories.tsx +54 -0
  19. package/src/AIContainer/AIContainer.tsx +35 -0
  20. package/src/AIContainer/AIContainerTypes.ts +14 -0
  21. package/src/AIContainer/__tests__/AIContainer.test.tsx +61 -0
  22. package/src/AIContainer/index.ts +2 -0
  23. package/src/EyebrowToken/EyebrowToken.tsx +1 -5
  24. package/src/MetricHighlight/MetricHighlight.stories.tsx +37 -14
  25. package/src/MetricHighlight/MetricHighlight.tsx +19 -99
  26. package/src/MetricHighlight/MetricHighlightTypes.ts +6 -9
  27. package/src/MetricHighlight/__tests__/MetricHighlight.test.tsx +13 -48
  28. package/src/NarrativeDataCallout/NarrativeDataCallout.stories.tsx +71 -0
  29. package/src/NarrativeDataCallout/NarrativeDataCallout.tsx +50 -0
  30. package/src/NarrativeDataCallout/NarrativeDataCalloutTypes.ts +18 -0
  31. package/src/NarrativeDataCallout/__tests__/NarrativeDataCallout.test.tsx +69 -0
  32. package/src/NarrativeDataCallout/index.ts +2 -0
  33. package/src/NarrativeDivider/NarrativeDivider.stories.tsx +33 -0
  34. package/src/NarrativeDivider/NarrativeDivider.tsx +30 -0
  35. package/src/NarrativeDivider/NarrativeDividerTypes.ts +4 -0
  36. package/src/NarrativeDivider/__tests__/NarrativeDivider.test.tsx +35 -0
  37. package/src/NarrativeDivider/index.ts +2 -0
  38. package/src/NarrativeHeadline/NarrativeHeadline.tsx +21 -12
  39. package/src/NarrativeHeadline/NarrativeHeadlineRoll.tsx +4 -1
  40. package/src/NarrativeHeadline/NarrativeHeadlineTypes.ts +9 -0
  41. package/src/NarrativeHeadline/__tests__/NarrativeHeadline.test.tsx +2 -7
  42. package/src/NarrativeHeadline/index.ts +1 -0
  43. package/src/NarrativeOrderedList/NarrativeOrderedList.stories.tsx +69 -0
  44. package/src/NarrativeOrderedList/NarrativeOrderedList.tsx +74 -0
  45. package/src/NarrativeOrderedList/NarrativeOrderedListTypes.ts +39 -0
  46. package/src/NarrativeOrderedList/__tests__/NarrativeOrderedList.test.tsx +89 -0
  47. package/src/NarrativeOrderedList/index.ts +6 -0
  48. package/src/NarrativeSummary/NarrativeSummary.stories.tsx +79 -0
  49. package/src/NarrativeSummary/NarrativeSummary.tsx +98 -0
  50. package/src/NarrativeSummary/NarrativeSummaryTypes.ts +59 -0
  51. package/src/NarrativeSummary/__tests__/NarrativeSummary.test.tsx +99 -0
  52. package/src/NarrativeSummary/index.ts +5 -0
  53. package/src/Playground/Playground.stories.tsx +174 -36
  54. package/src/PullQuote/PullQuote.stories.tsx +4 -1
  55. package/src/PullQuote/PullQuote.tsx +6 -3
  56. package/src/PullQuote/PullQuoteTypes.ts +1 -2
  57. package/src/ai-container.css +39 -0
  58. package/src/index.ts +25 -0
  59. package/src/metric-highlight.css +17 -27
  60. package/src/narrative-data-callout.css +103 -0
  61. package/src/narrative-divider.css +27 -0
  62. package/src/narrative-headline.css +7 -0
  63. package/src/narrative-ordered-list.css +112 -0
  64. package/src/narrative-summary.css +126 -0
  65. package/src/pull-quote.css +4 -25
@@ -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";
@@ -8,12 +8,34 @@ import NarrativeHeadlineRoll from "../NarrativeHeadline/NarrativeHeadlineRoll";
8
8
  import PullQuote from "../PullQuote/PullQuote";
9
9
  import MetricHighlight from "../MetricHighlight/MetricHighlight";
10
10
  import EyebrowToken from "../EyebrowToken/EyebrowToken";
11
+ import NarrativeDivider from "../NarrativeDivider/NarrativeDivider";
12
+ import NarrativeSummary from "../NarrativeSummary/NarrativeSummary";
13
+ import NarrativeOrderedList from "../NarrativeOrderedList/NarrativeOrderedList";
14
+ import NarrativeDataCallout from "../NarrativeDataCallout/NarrativeDataCallout";
15
+ import Button from "@sproutsocial/seeds-react-button";
11
16
  import Text from "@sproutsocial/seeds-react-text";
17
+ import { SparklineChart } from "@sproutsocial/seeds-react-data-viz/sparkline";
18
+ import { DonutChart } from "@sproutsocial/seeds-react-data-viz/donut";
12
19
  import "../narrative-container.css";
13
20
  import "../narrative-headline.css";
14
21
  import "../pull-quote.css";
15
22
  import "../metric-highlight.css";
16
23
  import "../eyebrow-token.css";
24
+ import "../narrative-divider.css";
25
+ import "../narrative-summary.css";
26
+ import "../narrative-ordered-list.css";
27
+ import "../narrative-data-callout.css";
28
+ import "@sproutsocial/seeds-react-button/dist/button.css";
29
+ import "@sproutsocial/seeds-react-data-viz/sparkline.css";
30
+
31
+ const spark = (data: number[], variant: "line" | "bar" = "line") => (
32
+ <SparklineChart
33
+ variant={variant}
34
+ description="Trend"
35
+ data={data}
36
+ className="h-full w-full"
37
+ />
38
+ );
17
39
 
18
40
  /**
19
41
  * Narrative Kit — Playground (showcase story)
@@ -40,56 +62,172 @@ const Attribution = ({ name }: { name: string }) => (
40
62
  const Brief = () => (
41
63
  <div className="box-border min-h-screen p-600 bg-app-bg-base">
42
64
  <div className="max-w-280 mx-auto grid gap-500">
43
- <span>
44
- <EyebrowToken className="mb-300 inline-flex">May 31, 2026</EyebrowToken>
45
- <NarrativeHeadline headingLevel={1}>
46
- The social suite built for{" "}
47
- <NarrativeHeadlineRoll
48
- words={["marketers", "agencies", "enterprise teams", "founders"]}
49
- />
50
- </NarrativeHeadline>
51
- <Text.BodyCopy mt={400} display="block" width="50ch">
52
- Empowering cross-functional stakeholders to drive engagement at scale and accelerate time-to-value through data-driven content strategies aligned with your brand's north-star KPIs.
53
- </Text.BodyCopy>
54
- </span>
55
-
56
-
65
+ <span>
66
+ <EyebrowToken className="mb-300 inline-flex">May 31, 2026</EyebrowToken>
67
+ <NarrativeHeadline headingLevel={1}>
68
+ The social suite built for{" "}
69
+ <NarrativeHeadlineRoll
70
+ words={["marketers", "agencies", "enterprise teams", "founders"]}
71
+ />
72
+ </NarrativeHeadline>
73
+ <Text.BodyCopy mt={400} display="block" width="50ch">
74
+ Empowering cross-functional stakeholders to drive engagement at scale
75
+ and accelerate time-to-value through data-driven content strategies
76
+ aligned with your brand's north-star KPIs.
77
+ </Text.BodyCopy>
78
+ </span>
57
79
 
58
80
  {/* Pull quotes ------------------------------------------------------- */}
59
81
  <NarrativeContainer className="w-full">
60
- <Grid columns={{ sm: 1, md: 1 }} gap={400}>
61
- {/* Metric highlights — a row of 3 -------------------------------- */}
62
- <Grid columns={{ sm: 1, md: 2, lg: 3 }} gap={400}>
63
- <MetricHighlight
64
- label="Impressions"
65
- value="1.2M"
66
- trend={{ direction: "up", value: "18%", "aria-label": "up 18%" }}
67
- data={[8, 12, 9, 15, 13, 19]}
68
- />
69
- <MetricHighlight
70
- label="Engagement rate"
71
- value="6.4%"
72
- trend={{ direction: "up", value: "240%", "aria-label": "up 240%" }}
73
- data={[3, 5, 4, 7, 6, 9]}
74
- />
75
- <MetricHighlight
76
- label="Response time"
77
- value="2h"
78
- trend={{ direction: "down", value: "12%", "aria-label": "down 12%" }}
79
- data={[18, 16, 17, 13, 14, 10]}
80
- />
81
- </Grid>
82
+ <Grid columns={{ sm: 1, md: 1 }} gap={500}>
83
+ {/* Metric highlights — a row of 3 -------------------------------- */}
84
+ <Grid columns={{ sm: 1, md: 2, lg: 3 }} gap={400}>
85
+ <MetricHighlight
86
+ label="Impressions"
87
+ value="1.2M"
88
+ trend={{ direction: "up", value: "18%", "aria-label": "up 18%" }}
89
+ sparkline={spark([8, 12, 9, 15, 13, 19])}
90
+ />
91
+ <MetricHighlight
92
+ label="Engagement rate"
93
+ value="6.4%"
94
+ trend={{
95
+ direction: "up",
96
+ value: "240%",
97
+ "aria-label": "up 240%",
98
+ }}
99
+ sparkline={spark([3, 5, 4, 7, 6, 9])}
100
+ />
101
+ <MetricHighlight
102
+ label="Response time"
103
+ value="2h"
104
+ trend={{
105
+ direction: "down",
106
+ value: "12%",
107
+ "aria-label": "down 12%",
108
+ }}
109
+ sparkline={spark([18, 16, 17, 13, 14, 10], "bar")}
110
+ />
111
+ </Grid>
112
+
113
+ {/* Divider — separates the metric row from the narrative below --- */}
114
+ <NarrativeDivider />
115
+
116
+ {/* Narrative summary (2-column) — between the pull quotes -------- */}
117
+ <NarrativeSummary
118
+ layout="two-column"
119
+ eyebrow="June 10th"
120
+ headline="Engagement climbed across every owned channel this month"
121
+ summary="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."
122
+ keyThemes={[
123
+ "Reply threads outperformed paid pushes for the week.",
124
+ "Average response time dropped nearly in half.",
125
+ "Enterprise segment drove the largest share of new engagement.",
126
+ ]}
127
+ />
128
+
82
129
  <PullQuote
83
130
  quote="The reply thread on the 18th outperformed our entire paid push for the week."
84
131
  attribution={<Attribution name="Jordan Lee" />}
85
132
  />
133
+
134
+ <NarrativeDivider />
135
+
136
+ <NarrativeOrderedList
137
+ items={[
138
+ {
139
+ title: "Double down on reply threads",
140
+ description:
141
+ "Shift a slice of paid budget toward the organic reply threads that outperformed this week.",
142
+ action: (
143
+ <Button appearance="ai-secondary">Suggested Action</Button>
144
+ ),
145
+ },
146
+ {
147
+ title: "Protect response time at peak",
148
+ description:
149
+ "Add coverage during the two peak windows to hold response time under two hours.",
150
+ action: (
151
+ <Button appearance="ai-secondary">Suggested Action</Button>
152
+ ),
153
+ },
154
+ {
155
+ title: "Lean into the enterprise segment",
156
+ description:
157
+ "Build a follow-up sequence for the enterprise cohort that drove the most new engagement.",
158
+ action: (
159
+ <Button appearance="ai-secondary">Suggested Action</Button>
160
+ ),
161
+ },
162
+ ]}
163
+ />
164
+
165
+ <NarrativeDivider />
166
+
167
+ {/* Narrative summary (3-column) — below the pull quotes --------- */}
168
+ <NarrativeSummary
169
+ eyebrow="Quarterly review"
170
+ headline="Three themes defined the quarter for the brand"
171
+ action={<Button appearance="primary">View report</Button>}
172
+ summary="Praesent pretium libero vitae arcu blandit pellentesque. Aenean fermentum, nisi eu blandit placerat, ligula ligula euismod ante, id semper nibh diam ac ipsum."
173
+ keyThemes={[
174
+ "Owned channels outpaced paid across every region.",
175
+ "Response time held under two hours through peak weeks.",
176
+ "Sentiment trended positive after the product launch.",
177
+ ]}
178
+ />
179
+
86
180
  <PullQuote
87
181
  appearance="vivid"
88
182
  quote="This rollout cut our average response time nearly in half."
89
183
  attribution={<Attribution name="Priya Nair" />}
90
184
  />
185
+
186
+ <NarrativeDivider />
187
+
188
+ {/* Data callout — a data-viz visual paired with narrative copy ---- */}
189
+ <NarrativeDataCallout
190
+ visual={
191
+ <DonutChart
192
+ description="Goal completion: 80% complete"
193
+ data={[
194
+ { name: "Complete", value: 80 },
195
+ { name: "Remaining", value: 20 },
196
+ ]}
197
+ />
198
+ }
199
+ >
200
+ We're 80% of the way to closing out this quarter's goal — a clear
201
+ signal that the cross-functional alignment we drove upstream is now
202
+ compounding into measurable downstream velocity. With the remaining
203
+ 20% squarely in view, we're doubling down on the workstreams that
204
+ move the needle and sunsetting the low-leverage noise to land this
205
+ initiative ahead of the curve.
206
+ </NarrativeDataCallout>
91
207
  </Grid>
92
208
  </NarrativeContainer>
209
+ <NarrativeContainer>
210
+ <NarrativeOrderedList
211
+ orientation="horizontal"
212
+ items={[
213
+ {
214
+ title: "Unlock audience momentum",
215
+ description:
216
+ "Reallocate engagement capacity toward the content clusters generating outsized share-of-voice gains across high-intent segments.",
217
+ },
218
+ {
219
+ title: "Operationalize your top performers",
220
+ description:
221
+ "Templatize the creative formats moving the needle this quarter and activate them across your highest-converting touchpoints.",
222
+ },
223
+ {
224
+ title: "Accelerate pipeline velocity",
225
+ description:
226
+ "Trigger a nurture sequence for the buyer personas showing the strongest conversion propensity signals right now.",
227
+ },
228
+ ]}
229
+ />
230
+ </NarrativeContainer>
93
231
  </div>
94
232
  </div>
95
233
  );
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
3
  import PullQuote from "./PullQuote";
4
4
  import "../pull-quote.css";
5
+ import "../ai-container.css";
5
6
 
6
7
  const meta = {
7
8
  title: "Narrative Kit/PullQuote",
@@ -16,7 +17,9 @@ type Story = StoryObj<typeof meta>;
16
17
 
17
18
  const Attribution = () => (
18
19
  <>
19
- <span aria-hidden className="text-400">🧵</span>
20
+ <span aria-hidden className="text-400">
21
+ 🧵
22
+ </span>
20
23
  <span>Display Name</span>
21
24
  </>
22
25
  );
@@ -8,9 +8,10 @@ import type { TypePullQuoteProps } from "./PullQuoteTypes";
8
8
  * optional attribution row (author name, avatar, network).
9
9
  *
10
10
  * Rendered as semantic `<figure>` / `<blockquote>` / `<figcaption>` markup.
11
- * Styled via `pull-quote.css` (Seeds CSS custom properties); consumers import
12
- * the classes through `@sproutsocial/racine/css/components`. Surface overrides
13
- * are done with standard `className` / `style`.
11
+ * Styled via `pull-quote.css` and `ai-container.css` (Seeds CSS custom
12
+ * properties); consumers import the classes through
13
+ * `@sproutsocial/racine/css/components`. Surface overrides are done with
14
+ * standard `className` / `style`.
14
15
  */
15
16
  const PullQuote = React.forwardRef<HTMLElement, TypePullQuoteProps>(
16
17
  ({ quote, attribution, appearance = "subtle", className, ...props }, ref) => {
@@ -19,6 +20,8 @@ const PullQuote = React.forwardRef<HTMLElement, TypePullQuoteProps>(
19
20
  ref={ref}
20
21
  className={cn(
21
22
  "seeds-pull-quote",
23
+ "seeds-ai-container",
24
+ { "seeds-ai-container-vivid": appearance === "vivid" },
22
25
  { "seeds-pull-quote-vivid": appearance === "vivid" },
23
26
  className
24
27
  )}
@@ -1,7 +1,6 @@
1
1
  import type * as React from "react";
2
2
 
3
- export interface TypePullQuoteProps
4
- extends React.HTMLAttributes<HTMLElement> {
3
+ export interface TypePullQuoteProps extends React.HTMLAttributes<HTMLElement> {
5
4
  /** The quotation text, rendered emphasized inside the accented bar. */
6
5
  quote: React.ReactNode;
7
6
  /**
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Seeds AIContainer component classes.
3
+ * Use these instead of writing out individual Tailwind utility classes.
4
+ *
5
+ * Requires @sproutsocial/seeds-react-theme/dist/theme-all.css imported for
6
+ * CSS variable definitions and dark mode support.
7
+ *
8
+ * Usage:
9
+ * <div class="seeds-ai-container">…</div> // subtle (default)
10
+ * <div class="seeds-ai-container seeds-ai-container-vivid">…</div>
11
+ */
12
+
13
+ @layer components {
14
+
15
+ .seeds-ai-container {
16
+ position: relative;
17
+ overflow: hidden;
18
+ box-sizing: border-box;
19
+ border-radius: var(--radius-800);
20
+ box-shadow: var(--shadow-low);
21
+ font-family: var(--font-family);
22
+ /* Subtle (default): the 20% AI gradient tint composited over the base
23
+ surface. --color-gradient-ai-subtle is already translucent, so layering it
24
+ over the opaque base color produces the soft wash. */
25
+ background-color: var(--color-container-bg-base);
26
+ background-image: var(--color-gradient-ai-subtle);
27
+ color: var(--color-text-headline);
28
+ }
29
+
30
+ /* Vivid: the full-saturation AI gradient with white content. White has no
31
+ semantic Seeds token; it is part of the AI gradient treatment (mirrors the
32
+ raw brand hex used in --color-gradient-ai). */
33
+ .seeds-ai-container-vivid {
34
+ background-color: transparent;
35
+ background-image: var(--color-gradient-ai);
36
+ color: #fff;
37
+ }
38
+
39
+ }
package/src/index.ts CHANGED
@@ -19,10 +19,35 @@ export { PullQuote, type TypePullQuoteProps } from "./PullQuote";
19
19
 
20
20
  export { EyebrowToken, type TypeEyebrowTokenProps } from "./EyebrowToken";
21
21
 
22
+ export {
23
+ NarrativeDivider,
24
+ type TypeNarrativeDividerProps,
25
+ } from "./NarrativeDivider";
26
+
22
27
  export {
23
28
  MetricHighlight,
24
29
  type TypeMetricHighlightProps,
25
30
  type TypeMetricHighlightTrend,
26
31
  } from "./MetricHighlight";
27
32
 
33
+ export {
34
+ NarrativeSummary,
35
+ type TypeNarrativeSummaryProps,
36
+ type TypeNarrativeSummaryLayout,
37
+ } from "./NarrativeSummary";
38
+
39
+ export {
40
+ NarrativeOrderedList,
41
+ type TypeNarrativeOrderedListProps,
42
+ type TypeNarrativeOrderedListItem,
43
+ type TypeNarrativeOrderedListOrientation,
44
+ } from "./NarrativeOrderedList";
45
+
46
+ export {
47
+ NarrativeDataCallout,
48
+ type TypeNarrativeDataCalloutProps,
49
+ } from "./NarrativeDataCallout";
50
+
51
+ export { AIContainer, type TypeAIContainerProps } from "./AIContainer";
52
+
28
53
  export type { TypeNarrativeTone } from "./_internal/types";
@@ -131,47 +131,37 @@
131
131
  stroke-width: 1.5;
132
132
  }
133
133
 
134
- /* Sparkline a single normalized polyline stretched to fit. The non-scaling
135
- stroke keeps a crisp 2px line at any aspect ratio. */
136
- .seeds-metric-highlight-spark {
137
- display: block;
134
+ /* Slot for an injected chart (e.g. a data-viz SparklineChart). Layout only — no
135
+ stroke/background styling, so the injected chart controls its own look. Heights
136
+ are explicit (never percentage/stretch) so a Highcharts chart always measures a
137
+ concrete container instead of falling back to its 400px default. */
138
+ .seeds-metric-highlight-chart {
139
+ box-sizing: border-box;
138
140
  flex: 0 0 auto;
139
- /* Matches the design's Data Viz / Categorical 01 line color (#0b968f). */
140
- stroke: var(--color-teal-700);
141
- }
142
-
143
- /* Downward trend: the red analog at the same scale step (#db3e3e). */
144
- .seeds-metric-highlight-spark-negative {
145
- stroke: var(--color-red-700);
146
- }
147
-
148
- .seeds-metric-highlight-spark path {
149
- stroke: inherit;
150
- stroke-width: 2;
151
- vector-effect: non-scaling-stroke;
141
+ align-self: center;
142
+ /* Center the injected chart as a flex item — an inline/inline-block child
143
+ (e.g. the data-viz SparklineChart) otherwise sits on the text baseline with
144
+ descender space below it, reading as vertically off-center in the slot. */
145
+ display: flex;
146
+ align-items: center;
152
147
  }
153
148
 
154
- /* Horizontal: a compact fixed-width chart beside the metric, matched to the
155
- metric block's height. */
156
- .seeds-metric-highlight:not(.seeds-metric-highlight-vertical) .seeds-metric-highlight-spark {
149
+ .seeds-metric-highlight:not(.seeds-metric-highlight-vertical) .seeds-metric-highlight-chart {
157
150
  width: 104px;
158
- align-self: stretch;
159
- min-height: 40px;
151
+ height: 48px;
160
152
  }
161
153
 
162
- .seeds-metric-highlight-small:not(.seeds-metric-highlight-vertical) .seeds-metric-highlight-spark {
154
+ .seeds-metric-highlight-small:not(.seeds-metric-highlight-vertical) .seeds-metric-highlight-chart {
163
155
  width: 88px;
164
156
  height: 40px;
165
- align-self: center;
166
157
  }
167
158
 
168
- /* Vertical: a full-width chart stacked beneath the metric. */
169
- .seeds-metric-highlight-vertical .seeds-metric-highlight-spark {
159
+ .seeds-metric-highlight-vertical .seeds-metric-highlight-chart {
170
160
  width: 100%;
171
161
  height: 96px;
172
162
  }
173
163
 
174
- .seeds-metric-highlight-vertical.seeds-metric-highlight-small .seeds-metric-highlight-spark {
164
+ .seeds-metric-highlight-vertical.seeds-metric-highlight-small .seeds-metric-highlight-chart {
175
165
  height: 56px;
176
166
  }
177
167
 
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Seeds NarrativeDataCallout component classes.
3
+ * Use these instead of writing out individual Tailwind utility classes.
4
+ *
5
+ * Requires @sproutsocial/seeds-react-theme/dist/theme-all.css imported for
6
+ * CSS variable definitions and dark mode support.
7
+ *
8
+ * The layout is a flex row driven by a container query on the root, so the
9
+ * visual/text columns stack based on the width the component is given, not the
10
+ * viewport.
11
+ *
12
+ * Usage:
13
+ * <div class="seeds-narrative-data-callout">
14
+ * <div class="seeds-narrative-data-callout-row">
15
+ * <div class="seeds-narrative-data-callout-visual">…chart…</div>
16
+ * <div class="seeds-narrative-data-callout-body">…copy…</div>
17
+ * </div>
18
+ * </div>
19
+ */
20
+
21
+ @layer components {
22
+
23
+ /* Establishes the query container. The row inside reacts to this element's
24
+ inline size. A container query only styles DESCENDANTS of the query container,
25
+ so the flex row lives on the inner element, not this one. */
26
+ .seeds-narrative-data-callout {
27
+ container-type: inline-size;
28
+ box-sizing: border-box;
29
+ font-family: var(--font-family);
30
+ }
31
+
32
+ /* Stacked single column by default (narrow). The container query below turns it
33
+ into the horizontal 1/3 + 2/3 row once there is room. */
34
+ .seeds-narrative-data-callout-row {
35
+ box-sizing: border-box;
36
+ display: flex;
37
+ flex-direction: column;
38
+ align-items: center;
39
+ gap: var(--space-450); /* 24px */
40
+ }
41
+
42
+ /* Visual slot — layout only, no stroke/background so the injected chart controls
43
+ its own look and its own size. No fixed height: charts like the data-viz
44
+ DonutChart carry their own intrinsic height (and a legend), so the slot sizes
45
+ to the chart rather than clipping it. Stacked full-width until the container
46
+ query expands the row. */
47
+ .seeds-narrative-data-callout-visual {
48
+ box-sizing: border-box;
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ width: 100%;
53
+ min-width: 0;
54
+ }
55
+
56
+ /* Stretch the injected visual to the slot's full width. A fluid-width chart like
57
+ the v2 DonutChart otherwise shrinks to content inside this centered flex slot,
58
+ so Highcharts fills that shrunk width and the ring gets width-capped below its
59
+ fixed 270px height — leaving an empty vertical band. Filling the width lets the
60
+ ring become height-capped and fill its box top-to-bottom, like the standalone
61
+ chart story. */
62
+ .seeds-narrative-data-callout-visual > * {
63
+ width: 100%;
64
+ }
65
+
66
+ /* Body copy — fills the remaining space. */
67
+ .seeds-narrative-data-callout-body {
68
+ box-sizing: border-box;
69
+ min-width: 0;
70
+ font-size: var(--font-size-300); /* 16px */
71
+ line-height: var(--line-height-400); /* ~28px */
72
+ font-weight: var(--font-weight-normal);
73
+ color: var(--color-text-body);
74
+ overflow-wrap: break-word;
75
+ }
76
+
77
+ /* Expand to the horizontal 1/3 + 2/3 split once the component is wide enough.
78
+ The threshold is on the component's own width (container query), so a narrow
79
+ callout on a wide screen still stacks. 480px keeps the donut + copy readable
80
+ side by side. */
81
+ @container (min-width: 480px) {
82
+ .seeds-narrative-data-callout-row {
83
+ flex-direction: row;
84
+ }
85
+
86
+ .seeds-narrative-data-callout-visual {
87
+ /* Nominally one-third, but never narrower than the injected chart's fixed
88
+ height (the v2 DonutChart is 270px tall). Below that width a fluid-width,
89
+ fixed-height chart like the donut gets width-capped and leaves an empty
90
+ vertical band above/below the ring; the min-width keeps the visual square
91
+ enough that the ring fills its box top-to-bottom, matching the standalone
92
+ chart story. */
93
+ flex: 0 0 33.333%;
94
+ min-width: 270px;
95
+ width: auto;
96
+ }
97
+
98
+ .seeds-narrative-data-callout-body {
99
+ flex: 1 1 0;
100
+ }
101
+ }
102
+
103
+ }