@sproutsocial/seeds-react-narrative-kit 0.1.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 (60) hide show
  1. package/.turbo/turbo-build.log +11 -11
  2. package/CHANGELOG.md +28 -0
  3. package/dist/esm/index.js +324 -2
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/eyebrow-token.css +37 -0
  6. package/dist/index.d.mts +304 -1
  7. package/dist/index.d.ts +304 -1
  8. package/dist/index.js +333 -3
  9. package/dist/index.js.map +1 -1
  10. package/dist/metric-highlight.css +168 -0
  11. package/dist/narrative-container.css +1 -1
  12. package/dist/narrative-divider.css +27 -0
  13. package/dist/narrative-headline.css +132 -0
  14. package/dist/narrative-summary.css +126 -0
  15. package/dist/pull-quote.css +89 -0
  16. package/package.json +13 -3
  17. package/src/EyebrowToken/EyebrowToken.stories.tsx +25 -0
  18. package/src/EyebrowToken/EyebrowToken.tsx +25 -0
  19. package/src/EyebrowToken/EyebrowTokenTypes.ts +7 -0
  20. package/src/EyebrowToken/__tests__/EyebrowToken.test.tsx +35 -0
  21. package/src/EyebrowToken/index.ts +2 -0
  22. package/src/MetricHighlight/MetricHighlight.stories.tsx +110 -0
  23. package/src/MetricHighlight/MetricHighlight.tsx +116 -0
  24. package/src/MetricHighlight/MetricHighlightTypes.ts +58 -0
  25. package/src/MetricHighlight/__tests__/MetricHighlight.test.tsx +116 -0
  26. package/src/MetricHighlight/index.ts +5 -0
  27. package/src/NarrativeContainer/NarrativeContainer.stories.tsx +3 -3
  28. package/src/NarrativeContainer/NarrativeContainer.tsx +1 -23
  29. package/src/NarrativeDivider/NarrativeDivider.stories.tsx +33 -0
  30. package/src/NarrativeDivider/NarrativeDivider.tsx +30 -0
  31. package/src/NarrativeDivider/NarrativeDividerTypes.ts +4 -0
  32. package/src/NarrativeDivider/__tests__/NarrativeDivider.test.tsx +35 -0
  33. package/src/NarrativeDivider/index.ts +2 -0
  34. package/src/NarrativeHeadline/NarrativeHeadline.stories.tsx +68 -0
  35. package/src/NarrativeHeadline/NarrativeHeadline.tsx +45 -0
  36. package/src/NarrativeHeadline/NarrativeHeadlineHighlight.tsx +31 -0
  37. package/src/NarrativeHeadline/NarrativeHeadlineRoll.tsx +103 -0
  38. package/src/NarrativeHeadline/NarrativeHeadlineTypes.ts +63 -0
  39. package/src/NarrativeHeadline/__tests__/NarrativeHeadline.test.tsx +133 -0
  40. package/src/NarrativeHeadline/index.ts +10 -0
  41. package/src/NarrativeSummary/NarrativeSummary.stories.tsx +79 -0
  42. package/src/NarrativeSummary/NarrativeSummary.tsx +98 -0
  43. package/src/NarrativeSummary/NarrativeSummaryTypes.ts +59 -0
  44. package/src/NarrativeSummary/__tests__/NarrativeSummary.test.tsx +99 -0
  45. package/src/NarrativeSummary/index.ts +5 -0
  46. package/src/Playground/Playground.stories.tsx +163 -0
  47. package/src/PullQuote/PullQuote.stories.tsx +45 -0
  48. package/src/PullQuote/PullQuote.tsx +42 -0
  49. package/src/PullQuote/PullQuoteTypes.ts +18 -0
  50. package/src/PullQuote/__tests__/PullQuote.test.tsx +48 -0
  51. package/src/PullQuote/index.ts +2 -0
  52. package/src/_internal/cn.ts +22 -0
  53. package/src/eyebrow-token.css +37 -0
  54. package/src/index.ts +31 -0
  55. package/src/metric-highlight.css +168 -0
  56. package/src/narrative-container.css +1 -1
  57. package/src/narrative-divider.css +27 -0
  58. package/src/narrative-headline.css +132 -0
  59. package/src/narrative-summary.css +126 -0
  60. package/src/pull-quote.css +89 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Seeds NarrativeHeadline 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
+ * <h2 class="seeds-narrative-headline">
10
+ * Headline with a
11
+ * <span class="seeds-narrative-headline-highlight">highlighted</span>
12
+ * word
13
+ * </h2>
14
+ */
15
+
16
+ @layer components {
17
+
18
+ /* Headline text. Rendered as an h1–h6, so reset the UA heading margins. */
19
+ .seeds-narrative-headline {
20
+ box-sizing: border-box;
21
+ margin: 0;
22
+ font-family: var(--font-family);
23
+ font-size: var(--font-size-700);
24
+ line-height: var(--line-height-700);
25
+ font-weight: var(--font-weight-bold);
26
+ letter-spacing: -0.02em;
27
+ color: var(--color-text-headline);
28
+ }
29
+
30
+ /* Compact size for denser layouts (e.g. NarrativeSummary). Weight, letter
31
+ spacing, and color are inherited from the base class. */
32
+ .seeds-narrative-headline-small {
33
+ font-size: var(--font-size-600); /* 24px */
34
+ line-height: var(--line-height-600); /* 32px */
35
+ }
36
+
37
+ /* Static gradient accent for a span of headline text ("highlighted word").
38
+ Paints the AI brand gradient onto the glyphs via background-clip. */
39
+ .seeds-narrative-headline-highlight {
40
+ background: var(--color-gradient-ai);
41
+ -webkit-background-clip: text;
42
+ background-clip: text;
43
+ color: transparent;
44
+ }
45
+
46
+ /* -------------------------------------------------------------------------
47
+ "Roll" variant — vertical word roll. The active word slides into view
48
+ while the previous one rolls out.
49
+ ------------------------------------------------------------------------- */
50
+ .seeds-narrative-headline-roll {
51
+ display: inline-block;
52
+ position: relative;
53
+ /* clip-path rather than overflow:hidden: overflow:hidden on an inline-block
54
+ snaps its baseline to the bottom margin edge, dropping the rolling word
55
+ below the surrounding text. clip-path keeps the natural baseline. */
56
+ clip-path: inset(0);
57
+ }
58
+
59
+ /* Invisible word that holds the box width to the active word. */
60
+ .seeds-narrative-headline-roll-sizer {
61
+ display: inline-block;
62
+ visibility: hidden;
63
+ white-space: nowrap;
64
+ pointer-events: none;
65
+ }
66
+
67
+ .seeds-narrative-headline-roll-word {
68
+ display: inline-block;
69
+ position: absolute;
70
+ top: 0;
71
+ left: 0;
72
+ white-space: nowrap;
73
+ transform: translateY(100%);
74
+ opacity: 0;
75
+ transition:
76
+ transform var(--seeds-narrative-headline-roll-ms, 500ms) ease,
77
+ opacity var(--seeds-narrative-headline-roll-ms, 500ms) ease;
78
+ }
79
+
80
+ .seeds-narrative-headline-roll-word-active {
81
+ transform: translateY(0);
82
+ opacity: 1;
83
+ }
84
+
85
+ .seeds-narrative-headline-roll-word-past {
86
+ transform: translateY(-100%);
87
+ opacity: 0;
88
+ }
89
+
90
+ /* Reverse the roll direction. */
91
+ .seeds-narrative-headline-roll-down .seeds-narrative-headline-roll-word {
92
+ transform: translateY(-100%);
93
+ }
94
+ .seeds-narrative-headline-roll-down .seeds-narrative-headline-roll-word-active {
95
+ transform: translateY(0);
96
+ }
97
+ .seeds-narrative-headline-roll-down .seeds-narrative-headline-roll-word-past {
98
+ transform: translateY(100%);
99
+ }
100
+
101
+ /* Gradient-painted rolling words with a slow shimmer. */
102
+ .seeds-narrative-headline-roll-gradient .seeds-narrative-headline-roll-word {
103
+ background: var(--color-gradient-ai);
104
+ background-size: 200% 200%;
105
+ -webkit-background-clip: text;
106
+ background-clip: text;
107
+ color: transparent;
108
+ animation: seeds-narrative-headline-grad-shift 8s ease infinite;
109
+ }
110
+
111
+ @keyframes seeds-narrative-headline-grad-shift {
112
+ 0%,
113
+ 100% {
114
+ background-position: 0% 50%;
115
+ }
116
+ 50% {
117
+ background-position: 100% 50%;
118
+ }
119
+ }
120
+
121
+ /* The JS cycle is already disabled under reduced-motion (the first word holds);
122
+ also flatten the transition and shimmer so any in-flight motion settles. */
123
+ @media (prefers-reduced-motion: reduce) {
124
+ .seeds-narrative-headline-roll-word {
125
+ transition: none;
126
+ }
127
+ .seeds-narrative-headline-roll-gradient .seeds-narrative-headline-roll-word {
128
+ animation: none;
129
+ }
130
+ }
131
+
132
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Seeds NarrativeSummary 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. Also relies on
7
+ * narrative-headline.css (headline) and eyebrow-token.css (eyebrow).
8
+ *
9
+ * The layout is a CSS grid driven by a container query on the root, so the
10
+ * columns collapse based on the width the component is given, not the viewport.
11
+ *
12
+ * Usage:
13
+ * <div class="seeds-narrative-summary">
14
+ * <div class="seeds-narrative-summary-grid">…</div>
15
+ * </div>
16
+ */
17
+
18
+ @layer components {
19
+
20
+ /* Establishes the query container. The grid inside reacts to this element's
21
+ inline size. */
22
+ .seeds-narrative-summary {
23
+ container-type: inline-size;
24
+ box-sizing: border-box;
25
+ font-family: var(--font-family);
26
+ }
27
+
28
+ /* Stacked single column by default (narrow). The container query below expands
29
+ it once there is room. */
30
+ .seeds-narrative-summary-grid {
31
+ display: grid;
32
+ grid-template-columns: 1fr;
33
+ gap: var(--space-450); /* 24px */
34
+ }
35
+
36
+ /* The lead column: eyebrow, headline, optional action. align-items:flex-start
37
+ keeps the eyebrow pill and the action sized to their content — without it the
38
+ column's default stretch would blow the eyebrow out to full width. The
39
+ headline still fills and wraps because its text is wider than the track. */
40
+ .seeds-narrative-summary-lead {
41
+ display: flex;
42
+ flex-direction: column;
43
+ align-items: flex-start;
44
+ gap: var(--space-300); /* 8px */
45
+ min-width: 0;
46
+ }
47
+
48
+ /* The headline area grows so the action anchors toward the bottom of the lead
49
+ column when the neighboring columns are taller. align-self:stretch fills the
50
+ track width (the lead column pins other items to flex-start) so the headline
51
+ wraps at the column edge regardless of its length. */
52
+ .seeds-narrative-summary-headline {
53
+ flex: 1 1 auto;
54
+ align-self: stretch;
55
+ }
56
+
57
+ /* In three-column mode the content wrapper is transparent to the grid, so its
58
+ two sections become grid items in their own columns. In two-column mode the
59
+ container query below turns it into a real, stacking grid item. */
60
+ .seeds-narrative-summary-content {
61
+ display: contents;
62
+ }
63
+
64
+ .seeds-narrative-summary-section {
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: var(--space-300); /* 8px */
68
+ min-width: 0;
69
+ }
70
+
71
+ /* Section label. 14px / 24px has no exact type token (the scale skips from
72
+ 13px to 16px), so the design value is kept verbatim — same rationale as
73
+ eyebrow-token.css. All other values use tokens. */
74
+ .seeds-narrative-summary-label {
75
+ margin: 0;
76
+ font-size: 14px;
77
+ line-height: 24px;
78
+ font-weight: var(--font-weight-bold);
79
+ color: var(--color-text-headline);
80
+ }
81
+
82
+ .seeds-narrative-summary-text {
83
+ margin: 0;
84
+ font-size: 14px; /* off-scale, see label above */
85
+ line-height: 24px;
86
+ font-weight: var(--font-weight-normal);
87
+ color: var(--color-text-body);
88
+ }
89
+
90
+ .seeds-narrative-summary-list {
91
+ margin: 0;
92
+ padding-inline-start: 21px;
93
+ list-style: disc;
94
+ font-size: 14px; /* off-scale, see label above */
95
+ line-height: 24px;
96
+ font-weight: var(--font-weight-normal);
97
+ color: var(--color-text-body);
98
+ }
99
+
100
+ .seeds-narrative-summary-list li {
101
+ margin: 0;
102
+ }
103
+
104
+ /* Expand to the multi-column layout once the component is wide enough. The
105
+ threshold is on the component's own width (container query), so a narrow card
106
+ on a wide screen still stacks. 640px = Tailwind/Seeds sm breakpoint. */
107
+ @container (min-width: 640px) {
108
+ .seeds-narrative-summary-grid {
109
+ grid-template-columns: repeat(3, 1fr);
110
+ }
111
+
112
+ /* 1/3 lead + 2/3 content. */
113
+ .seeds-narrative-summary-two-column {
114
+ grid-template-columns: 1fr 2fr;
115
+ }
116
+
117
+ /* The content wrapper becomes a real grid item that stacks its two sections. */
118
+ .seeds-narrative-summary-two-column .seeds-narrative-summary-content {
119
+ display: flex;
120
+ flex-direction: column;
121
+ gap: var(--space-450); /* 24px */
122
+ min-width: 0;
123
+ }
124
+ }
125
+
126
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Seeds PullQuote 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
+ * <figure class="seeds-pull-quote">…</figure> // subtle (default)
10
+ * <figure class="seeds-pull-quote seeds-pull-quote-vivid">…</figure>
11
+ */
12
+
13
+ @layer components {
14
+
15
+ .seeds-pull-quote {
16
+ position: relative;
17
+ overflow: hidden;
18
+ box-sizing: border-box;
19
+ width: 100%;
20
+ display: flex;
21
+ flex-direction: column;
22
+ align-items: flex-start;
23
+ margin: 0; /* reset the browser default <figure> margin */
24
+ /* 40px block / 48px inline — 48px is off the space scale, so derive it from
25
+ two tokens (40 + 8) rather than hardcoding an off-scale value. */
26
+ padding: var(--space-600) calc(var(--space-600) + var(--space-300));
27
+ border-radius: var(--radius-800);
28
+ box-shadow: var(--shadow-low);
29
+ font-family: var(--font-family);
30
+ /* Subtle (default): the 20% AI gradient tint composited over the base
31
+ surface. --color-gradient-ai-subtle is already translucent, so layering it
32
+ over the opaque base color produces the soft wash. */
33
+ background-color: var(--color-container-bg-base);
34
+ background-image: var(--color-gradient-ai-subtle);
35
+ color: var(--color-text-headline);
36
+ }
37
+
38
+ /* Vivid: the full-saturation AI gradient with white content. White has no
39
+ semantic Seeds token; it is part of the AI gradient treatment (mirrors the
40
+ raw brand hex used in --color-gradient-ai). */
41
+ .seeds-pull-quote-vivid {
42
+ background-color: transparent;
43
+ background-image: var(--color-gradient-ai);
44
+ color: #fff;
45
+ }
46
+
47
+ /* The accented bar holding the quote and attribution. */
48
+ .seeds-pull-quote-bar {
49
+ box-sizing: border-box;
50
+ display: flex;
51
+ flex-direction: column;
52
+ align-items: flex-start;
53
+ gap: var(--space-450); /* 24px */
54
+ width: 100%;
55
+ padding-left: var(--space-450); /* 24px */
56
+ /* 4px is off the border-width scale (max token is 3px); the bar is a
57
+ decorative accent, mirroring NarrativeContainer's hardcoded 4px ring. The
58
+ subtle accent uses the AI gradient mid stop (#9747ff — no exact token). */
59
+ border-left: 4px solid #9747ff;
60
+ }
61
+
62
+ .seeds-pull-quote-vivid .seeds-pull-quote-bar {
63
+ border-left-color: #fff;
64
+ }
65
+
66
+ .seeds-pull-quote-text {
67
+ margin: 0; /* reset the browser default <blockquote> margin */
68
+ font-size: var(--font-size-600);
69
+ line-height: var(--line-height-600);
70
+ font-style: italic;
71
+ font-weight: var(--font-weight-normal);
72
+ color: inherit;
73
+ overflow-wrap: break-word;
74
+ }
75
+
76
+ /* Attribution row — a flex container for author name, avatar, network, etc.
77
+ Direct text inherits a bold, small treatment to match the display-name style;
78
+ richer content (e.g. an Avatar) composes freely. */
79
+ .seeds-pull-quote-attribution {
80
+ display: flex;
81
+ align-items: center;
82
+ gap: var(--space-300); /* 8px */
83
+ font-size: var(--font-size-200);
84
+ line-height: var(--line-height-200);
85
+ font-weight: var(--font-weight-bold);
86
+ color: inherit;
87
+ }
88
+
89
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-narrative-kit",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Seeds React Narrative Kit — composable narrative primitives for executive-brief style pages",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
@@ -13,10 +13,16 @@
13
13
  "import": "./dist/esm/index.js",
14
14
  "require": "./dist/index.js"
15
15
  },
16
- "./dist/narrative-container.css": "./dist/narrative-container.css"
16
+ "./dist/narrative-container.css": "./dist/narrative-container.css",
17
+ "./dist/narrative-headline.css": "./dist/narrative-headline.css",
18
+ "./dist/pull-quote.css": "./dist/pull-quote.css",
19
+ "./dist/metric-highlight.css": "./dist/metric-highlight.css",
20
+ "./dist/eyebrow-token.css": "./dist/eyebrow-token.css",
21
+ "./dist/narrative-summary.css": "./dist/narrative-summary.css",
22
+ "./dist/narrative-divider.css": "./dist/narrative-divider.css"
17
23
  },
18
24
  "scripts": {
19
- "build": "tsup --dts && cp src/narrative-container.css dist/narrative-container.css",
25
+ "build": "tsup --dts && cp src/narrative-container.css dist/narrative-container.css && cp src/narrative-headline.css dist/narrative-headline.css && cp src/pull-quote.css dist/pull-quote.css && cp src/metric-highlight.css dist/metric-highlight.css && cp src/eyebrow-token.css dist/eyebrow-token.css && cp src/narrative-summary.css dist/narrative-summary.css && cp src/narrative-divider.css dist/narrative-divider.css",
20
26
  "build:debug": "tsup --dts --metafile",
21
27
  "dev": "tsup --watch --dts",
22
28
  "clean": "rm -rf .turbo dist",
@@ -30,6 +36,10 @@
30
36
  },
31
37
  "devDependencies": {
32
38
  "@sproutsocial/eslint-config-seeds": "*",
39
+ "@sproutsocial/seeds-react-button": "*",
40
+ "@sproutsocial/seeds-react-data-viz": "*",
41
+ "@sproutsocial/seeds-react-grid": "^0.2.5",
42
+ "@sproutsocial/seeds-react-text": "*",
33
43
  "@sproutsocial/seeds-react-testing-library": "*",
34
44
  "@sproutsocial/seeds-testing": "*",
35
45
  "@sproutsocial/seeds-tsconfig": "*",
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import EyebrowToken from "./EyebrowToken";
4
+ import "../eyebrow-token.css";
5
+
6
+ const meta = {
7
+ title: "Narrative Kit/EyebrowToken",
8
+ component: EyebrowToken,
9
+ args: {
10
+ children: "May 31, 2026",
11
+ },
12
+ } satisfies Meta<typeof EyebrowToken>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ /** A small gray pill carrying a date — the typical eyebrow above a headline. */
18
+ export const Default: Story = {};
19
+
20
+ /** A category kicker instead of a date. */
21
+ export const Category: Story = {
22
+ args: {
23
+ children: "Quarterly review",
24
+ },
25
+ };
@@ -0,0 +1,25 @@
1
+ import * as React from "react";
2
+ import { cn } from "../_internal/cn";
3
+ import type { TypeEyebrowTokenProps } from "./EyebrowTokenTypes";
4
+
5
+ /**
6
+ * EyebrowToken — a small gray pill that sits above a headline to carry a short
7
+ * bit of context: a date (e.g. "May 31, 2026"), category, or kicker.
8
+ *
9
+ * A faithful match of the Figma "Token / Non Clickable / Default" design — a
10
+ * solid `#dee1e1` fill with a matching border, 6px radius, and small body text.
11
+ * Styled via `eyebrow-token.css` (Seeds CSS custom properties); consumers import
12
+ * the classes through `@sproutsocial/racine/css/components`. Overrides come via
13
+ * standard `className` / `style`.
14
+ */
15
+ const EyebrowToken = React.forwardRef<HTMLSpanElement, TypeEyebrowTokenProps>(
16
+ ({ children, className, ...props }, ref) => (
17
+ <span ref={ref} className={cn("seeds-eyebrow-token", className)} {...props}>
18
+ {children}
19
+ </span>
20
+ )
21
+ );
22
+
23
+ EyebrowToken.displayName = "EyebrowToken";
24
+
25
+ export default EyebrowToken;
@@ -0,0 +1,7 @@
1
+ import type * as React from "react";
2
+
3
+ export interface TypeEyebrowTokenProps
4
+ extends React.HTMLAttributes<HTMLSpanElement> {
5
+ /** The eyebrow text — a date, category, or short kicker. */
6
+ children: React.ReactNode;
7
+ }
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import { render, screen } from "@sproutsocial/seeds-react-testing-library";
3
+ import EyebrowToken from "../EyebrowToken";
4
+
5
+ describe("EyebrowToken", () => {
6
+ it("renders its children", () => {
7
+ render(<EyebrowToken>May 31, 2026</EyebrowToken>);
8
+
9
+ expect(screen.getByText("May 31, 2026")).toBeInTheDocument();
10
+ });
11
+
12
+ it("merges a consumer className with the base class", () => {
13
+ const { container } = render(
14
+ <EyebrowToken className="custom">May 31, 2026</EyebrowToken>
15
+ );
16
+
17
+ const root = container.querySelector(".seeds-eyebrow-token");
18
+ expect(root).toBeInTheDocument();
19
+ expect(root).toHaveClass("custom");
20
+ });
21
+
22
+ it("forwards extra props to the underlying element", () => {
23
+ render(<EyebrowToken data-testid="eyebrow">May 31, 2026</EyebrowToken>);
24
+
25
+ expect(screen.getByTestId("eyebrow")).toBeInTheDocument();
26
+ });
27
+
28
+ it("forwards its ref to the underlying span element", () => {
29
+ const ref = React.createRef<HTMLSpanElement>();
30
+ render(<EyebrowToken ref={ref}>May 31, 2026</EyebrowToken>);
31
+
32
+ expect(ref.current).toBeInstanceOf(HTMLSpanElement);
33
+ expect(ref.current?.tagName).toBe("SPAN");
34
+ });
35
+ });
@@ -0,0 +1,2 @@
1
+ export { default as EyebrowToken } from "./EyebrowToken";
2
+ export type { TypeEyebrowTokenProps } from "./EyebrowTokenTypes";
@@ -0,0 +1,110 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { SparklineChart } from "@sproutsocial/seeds-react-data-viz/sparkline";
4
+ import MetricHighlight from "./MetricHighlight";
5
+ import "../metric-highlight.css";
6
+ import "@sproutsocial/seeds-react-data-viz/sparkline.css";
7
+
8
+ /**
9
+ * MetricHighlight has no built-in chart — the `sparkline` slot accepts any node.
10
+ * These stories inject the real `SparklineChart` from
11
+ * `@sproutsocial/seeds-react-data-viz`; narrative-kit takes no runtime
12
+ * dependency on data-viz (only these stories do, via a devDependency). The
13
+ * injected chart fills the slot with `className="h-full w-full"`.
14
+ */
15
+ const RISING = [4, 6, 5, 8, 7, 10];
16
+ const FALLING = [18, 16, 17, 13, 14, 10];
17
+
18
+ const spark = (
19
+ data: number[],
20
+ variant: "line" | "bar" = "line"
21
+ ): React.ReactNode => (
22
+ <SparklineChart
23
+ variant={variant}
24
+ description="Weekly trend"
25
+ data={data}
26
+ className="h-full w-full"
27
+ />
28
+ );
29
+
30
+ const meta = {
31
+ title: "Narrative Kit/MetricHighlight",
32
+ component: MetricHighlight,
33
+ args: {
34
+ label: "Insight metric",
35
+ value: "123",
36
+ trend: { direction: "up", value: "240%", "aria-label": "up 240%" },
37
+ sparkline: spark(RISING),
38
+ },
39
+ } satisfies Meta<typeof MetricHighlight>;
40
+
41
+ export default meta;
42
+ type Story = StoryObj<typeof meta>;
43
+
44
+ /** Horizontal, default size, padded surface, numeric trend badge, and a sparkline. */
45
+ export const Default: Story = {
46
+ args: {
47
+ className: "w-80",
48
+ },
49
+ };
50
+
51
+ /** Condensed: label and value share a single row. */
52
+ export const Small: Story = {
53
+ args: {
54
+ className: "w-80",
55
+ size: "small",
56
+ },
57
+ };
58
+
59
+ /** Stacked: metric block above a full-width sparkline. */
60
+ export const Vertical: Story = {
61
+ args: {
62
+ className: "w-80",
63
+ appearance: "vertical",
64
+ },
65
+ };
66
+
67
+ /** No surface — composes inside another container. */
68
+ export const WithoutContainer: Story = {
69
+ args: {
70
+ className: "w-80",
71
+ container: false,
72
+ },
73
+ };
74
+
75
+ /** Metric on its own — omit the `sparkline` slot to drop the chart. */
76
+ export const WithoutChart: Story = {
77
+ args: {
78
+ className: "w-80",
79
+ sparkline: undefined,
80
+ },
81
+ };
82
+
83
+ /**
84
+ * Negative trend — the badge swaps to the red treatment and the sparkline (which
85
+ * auto-colors from its data) slopes downward in red.
86
+ */
87
+ export const TrendDown: Story = {
88
+ args: {
89
+ className: "w-80",
90
+ value: "87",
91
+ trend: { direction: "down", value: "12%", "aria-label": "down 12%" },
92
+ sparkline: spark(FALLING),
93
+ },
94
+ };
95
+
96
+ /** Icon-only trend badge (no number). */
97
+ export const TrendIconOnly: Story = {
98
+ args: {
99
+ className: "w-80",
100
+ trend: { direction: "up", "aria-label": "trending up" },
101
+ },
102
+ };
103
+
104
+ /** The slot accepts the bar variant of the sparkline just as well. */
105
+ export const BarSparkline: Story = {
106
+ args: {
107
+ className: "w-80",
108
+ sparkline: spark(RISING, "bar"),
109
+ },
110
+ };