@sproutsocial/seeds-react-narrative-kit 0.4.0 → 0.5.1

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.
@@ -0,0 +1,80 @@
1
+ import * as React from "react";
2
+ import { cn } from "../_internal/cn";
3
+ import NarrativeAttribution from "../NarrativeAttribution/NarrativeAttribution";
4
+ import type { TypeNarrativeSocialMediaCardProps } from "./NarrativeSocialMediaCardTypes";
5
+
6
+ /**
7
+ * NarrativeSocialMediaCard — a narrative primitive that displays a social media
8
+ * post card with a header (avatar + handle), content area, and footer with
9
+ * engagement metadata (replies, likes, reposts, etc.).
10
+ *
11
+ * Styled via narrative-social-media-card.css (Seeds CSS custom properties);
12
+ * consumers import the classes through @sproutsocial/racine/css/components.
13
+ * Layout is vertically stacked with clean typography and icon-based metadata in
14
+ * the footer. Default has a gray background; use appearance="no-background" to
15
+ * remove the background and blend seamlessly with the page.
16
+ */
17
+ const NarrativeSocialMediaCard = React.forwardRef<
18
+ HTMLDivElement,
19
+ TypeNarrativeSocialMediaCardProps
20
+ >(
21
+ (
22
+ {
23
+ avatar,
24
+ avatarAlt,
25
+ displayName,
26
+ socialIcon,
27
+ image,
28
+ text,
29
+ meta,
30
+ appearance,
31
+ className,
32
+ ...props
33
+ },
34
+ ref
35
+ ) => {
36
+ return (
37
+ <div
38
+ ref={ref}
39
+ className={cn(
40
+ "seeds-narrative-social-media-card",
41
+ appearance === "no-background" &&
42
+ "seeds-narrative-social-media-card--no-background",
43
+ className
44
+ )}
45
+ {...props}
46
+ >
47
+ <NarrativeAttribution
48
+ avatarUrl={avatar}
49
+ avatarAlt={avatarAlt}
50
+ userName={displayName}
51
+ socialIcon={socialIcon}
52
+ />
53
+
54
+ <div className="seeds-narrative-social-media-card-content">
55
+ {image}
56
+ {text}
57
+ </div>
58
+
59
+ <div className="seeds-narrative-social-media-card-footer">
60
+ {meta.map((item, index) => (
61
+ <div
62
+ key={index}
63
+ className="seeds-narrative-social-media-card-stat"
64
+ aria-label={item.label}
65
+ >
66
+ {item.icon}
67
+ <span className="seeds-narrative-social-media-card-stat-value">
68
+ {item.value}
69
+ </span>
70
+ </div>
71
+ ))}
72
+ </div>
73
+ </div>
74
+ );
75
+ }
76
+ );
77
+
78
+ NarrativeSocialMediaCard.displayName = "NarrativeSocialMediaCard";
79
+
80
+ export default NarrativeSocialMediaCard;
@@ -0,0 +1,30 @@
1
+ import type * as React from "react";
2
+
3
+ export interface TypeNarrativeSocialMediaCardMeta {
4
+ /** Icon for the metadata (e.g., reply, heart, repost). */
5
+ icon: React.ReactNode;
6
+ /** Number or formatted string value for the metadata. */
7
+ value: number | string;
8
+ /** Optional accessible label for the metadata. */
9
+ label?: string;
10
+ }
11
+
12
+ export interface TypeNarrativeSocialMediaCardProps
13
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
14
+ /** Avatar image URL for the social media profile. */
15
+ avatar: string;
16
+ /** Alt text for the avatar image. */
17
+ avatarAlt: string;
18
+ /** Display name for the social media profile. */
19
+ displayName: string;
20
+ /** Social media network icon (e.g., Twitter, Instagram, LinkedIn). */
21
+ socialIcon: React.ReactNode;
22
+ /** Optional image to display above the text content. */
23
+ image?: React.ReactNode;
24
+ /** The text content of the social media post. */
25
+ text: React.ReactNode;
26
+ /** Array of metadata with icons and values (e.g., replies, likes, reposts). */
27
+ meta: TypeNarrativeSocialMediaCardMeta[];
28
+ /** Visual appearance of the card. Default has gray background, "no-background" removes the background. */
29
+ appearance?: "no-background";
30
+ }
@@ -0,0 +1,154 @@
1
+ import React from "react";
2
+ import { render, screen } from "@sproutsocial/seeds-react-testing-library";
3
+ import NarrativeSocialMediaCard from "../NarrativeSocialMediaCard";
4
+ import { PartnerLogo } from "@sproutsocial/seeds-react-partner-logo";
5
+
6
+ // Test meta icons
7
+ const TestIcon1 = () => (
8
+ <svg viewBox="0 0 16 16" aria-hidden focusable={false}>
9
+ <path d="M2 8H14" stroke="currentColor" strokeWidth="1.5" />
10
+ </svg>
11
+ );
12
+
13
+ const TestIcon2 = () => (
14
+ <svg viewBox="0 0 16 16" aria-hidden focusable={false}>
15
+ <circle cx="8" cy="8" r="6" stroke="currentColor" strokeWidth="1.5" />
16
+ </svg>
17
+ );
18
+
19
+ const TestIcon3 = () => (
20
+ <svg viewBox="0 0 16 16" aria-hidden focusable={false}>
21
+ <rect
22
+ x="4"
23
+ y="4"
24
+ width="8"
25
+ height="8"
26
+ stroke="currentColor"
27
+ strokeWidth="1.5"
28
+ />
29
+ </svg>
30
+ );
31
+
32
+ describe("NarrativeSocialMediaCard", () => {
33
+ const defaultProps = {
34
+ avatar: "https://example.com/avatar.jpg",
35
+ avatarAlt: "Profile avatar",
36
+ displayName: "Display Name",
37
+ socialIcon: <PartnerLogo partnerName="twitter" size="small" />,
38
+ text: "Test content",
39
+ meta: [
40
+ { icon: <TestIcon1 />, value: 42, label: "Action 1" },
41
+ { icon: <TestIcon2 />, value: 128, label: "Action 2" },
42
+ { icon: <TestIcon3 />, value: 17, label: "Action 3" },
43
+ ],
44
+ };
45
+
46
+ it("renders the avatar image", () => {
47
+ render(<NarrativeSocialMediaCard {...defaultProps} />);
48
+
49
+ const avatar = screen.getByRole("img");
50
+ expect(avatar).toBeInTheDocument();
51
+ expect(avatar).toHaveAttribute("src", "https://example.com/avatar.jpg");
52
+ });
53
+
54
+ it("renders the display name", () => {
55
+ render(<NarrativeSocialMediaCard {...defaultProps} />);
56
+
57
+ expect(screen.getByText("Display Name")).toBeInTheDocument();
58
+ });
59
+
60
+ it("renders the text content", () => {
61
+ render(
62
+ <NarrativeSocialMediaCard
63
+ {...defaultProps}
64
+ text="This is a test post with some content"
65
+ />
66
+ );
67
+
68
+ expect(
69
+ screen.getByText("This is a test post with some content")
70
+ ).toBeInTheDocument();
71
+ });
72
+
73
+ it("renders the meta values in the footer", () => {
74
+ render(<NarrativeSocialMediaCard {...defaultProps} />);
75
+
76
+ expect(screen.getByText("42")).toBeInTheDocument();
77
+ expect(screen.getByText("128")).toBeInTheDocument();
78
+ expect(screen.getByText("17")).toBeInTheDocument();
79
+ });
80
+
81
+ it("accepts string values for meta values", () => {
82
+ render(
83
+ <NarrativeSocialMediaCard
84
+ avatar="https://example.com/avatar.jpg"
85
+ avatarAlt="Profile avatar"
86
+ displayName="Display Name"
87
+ socialIcon={<PartnerLogo partnerName="twitter" size="small" />}
88
+ text="Test content"
89
+ meta={[
90
+ { icon: <TestIcon1 />, value: "1.2K", label: "Action 1" },
91
+ { icon: <TestIcon2 />, value: "5.3K", label: "Action 2" },
92
+ { icon: <TestIcon3 />, value: "842", label: "Action 3" },
93
+ ]}
94
+ />
95
+ );
96
+
97
+ expect(screen.getByText("1.2K")).toBeInTheDocument();
98
+ expect(screen.getByText("5.3K")).toBeInTheDocument();
99
+ expect(screen.getByText("842")).toBeInTheDocument();
100
+ });
101
+
102
+ it("renders the avatarAlt attribute", () => {
103
+ render(
104
+ <NarrativeSocialMediaCard
105
+ {...defaultProps}
106
+ avatarAlt="Custom avatar description"
107
+ />
108
+ );
109
+
110
+ const avatar = screen.getByRole("img");
111
+ expect(avatar).toHaveAttribute("alt", "Custom avatar description");
112
+ });
113
+
114
+ it("merges a consumer className with the base class", () => {
115
+ const { container } = render(
116
+ <NarrativeSocialMediaCard {...defaultProps} className="custom" />
117
+ );
118
+
119
+ const root = container.querySelector(".seeds-narrative-social-media-card");
120
+ expect(root).toBeInTheDocument();
121
+ expect(root).toHaveClass("custom");
122
+ });
123
+
124
+ it("forwards extra props to the underlying element", () => {
125
+ render(
126
+ <NarrativeSocialMediaCard {...defaultProps} data-testid="social-card" />
127
+ );
128
+
129
+ expect(screen.getByTestId("social-card")).toBeInTheDocument();
130
+ });
131
+
132
+ it("forwards its ref to the underlying div element", () => {
133
+ const ref = React.createRef<HTMLDivElement>();
134
+ render(<NarrativeSocialMediaCard {...defaultProps} ref={ref} />);
135
+
136
+ expect(ref.current).toBeInstanceOf(HTMLDivElement);
137
+ expect(ref.current?.tagName).toBe("DIV");
138
+ });
139
+
140
+ it("renders image above text when image is provided", () => {
141
+ const { container } = render(
142
+ <NarrativeSocialMediaCard
143
+ {...defaultProps}
144
+ image={<img src="test.jpg" alt="test" />}
145
+ text="Text content"
146
+ />
147
+ );
148
+
149
+ const content = container.querySelector(
150
+ ".seeds-narrative-social-media-card-content"
151
+ );
152
+ expect(content?.firstChild).toHaveProperty("tagName", "IMG");
153
+ });
154
+ });
@@ -0,0 +1,5 @@
1
+ export { default as NarrativeSocialMediaCard } from "./NarrativeSocialMediaCard";
2
+ export type {
3
+ TypeNarrativeSocialMediaCardProps,
4
+ TypeNarrativeSocialMediaCardMeta,
5
+ } from "./NarrativeSocialMediaCardTypes";
@@ -12,10 +12,13 @@ import NarrativeDivider from "../NarrativeDivider/NarrativeDivider";
12
12
  import NarrativeSummary from "../NarrativeSummary/NarrativeSummary";
13
13
  import NarrativeOrderedList from "../NarrativeOrderedList/NarrativeOrderedList";
14
14
  import NarrativeDataCallout from "../NarrativeDataCallout/NarrativeDataCallout";
15
+ import NarrativeSocialMediaCard from "../NarrativeSocialMediaCard/NarrativeSocialMediaCard";
15
16
  import Button from "@sproutsocial/seeds-react-button";
16
17
  import Text from "@sproutsocial/seeds-react-text";
17
18
  import { SparklineChart } from "@sproutsocial/seeds-react-data-viz/sparkline";
18
19
  import { DonutChart } from "@sproutsocial/seeds-react-data-viz/donut";
20
+ import { PartnerLogo } from "@sproutsocial/seeds-react-partner-logo";
21
+ import Icon from "@sproutsocial/seeds-react-icon";
19
22
  import "../narrative-container.css";
20
23
  import "../narrative-headline.css";
21
24
  import "../pull-quote.css";
@@ -25,6 +28,7 @@ import "../narrative-divider.css";
25
28
  import "../narrative-summary.css";
26
29
  import "../narrative-ordered-list.css";
27
30
  import "../narrative-data-callout.css";
31
+ import "../narrative-social-media-card.css";
28
32
  import "@sproutsocial/seeds-react-button/dist/button.css";
29
33
  import "@sproutsocial/seeds-react-data-viz/sparkline.css";
30
34
 
@@ -204,8 +208,87 @@ const Brief = () => (
204
208
  move the needle and sunsetting the low-leverage noise to land this
205
209
  initiative ahead of the curve.
206
210
  </NarrativeDataCallout>
211
+
212
+ <NarrativeDivider />
213
+
214
+ {/* Social media cards — a row of 3 sample posts -------------------- */}
215
+ <Grid columns={{ sm: 1, md: 2, lg: 3 }} gap={400}>
216
+ <NarrativeSocialMediaCard
217
+ avatar="https://api.dicebear.com/7.x/avataaars/svg?seed=Felix"
218
+ avatarAlt="Sprout Social avatar"
219
+ displayName="Sprout Social"
220
+ socialIcon={<PartnerLogo partnerName="twitter" size="small" />}
221
+ text="Just launched our new product! Check it out and let us know what you think."
222
+ meta={[
223
+ {
224
+ icon: <Icon name="reply-outline" />,
225
+ value: 12,
226
+ label: "Replies",
227
+ },
228
+ {
229
+ icon: <Icon name="heart-outline" />,
230
+ value: 128,
231
+ label: "Likes",
232
+ },
233
+ {
234
+ icon: <Icon name="repost-outline" />,
235
+ value: 42,
236
+ label: "Reposts",
237
+ },
238
+ ]}
239
+ />
240
+ <NarrativeSocialMediaCard
241
+ avatar="https://api.dicebear.com/7.x/avataaars/svg?seed=Jordan"
242
+ avatarAlt="Marketing Team avatar"
243
+ displayName="Marketing Team"
244
+ socialIcon={<PartnerLogo partnerName="linkedin" size="small" />}
245
+ text="Our most viral post ever! Thanks for all the engagement. The community response has been incredible and we're excited to keep the momentum going."
246
+ meta={[
247
+ {
248
+ icon: <Icon name="reply-outline" />,
249
+ value: 45,
250
+ label: "Comments",
251
+ },
252
+ {
253
+ icon: <Icon name="heart-outline" />,
254
+ value: 892,
255
+ label: "Likes",
256
+ },
257
+ {
258
+ icon: <Icon name="repost-outline" />,
259
+ value: 156,
260
+ label: "Shares",
261
+ },
262
+ ]}
263
+ />
264
+ <NarrativeSocialMediaCard
265
+ avatar="https://api.dicebear.com/7.x/avataaars/svg?seed=Alex"
266
+ avatarAlt="Brand Studio avatar"
267
+ displayName="Brand Studio"
268
+ socialIcon={<PartnerLogo partnerName="instagram" size="small" />}
269
+ text="Behind the scenes of today's campaign launch. Swipe to see the creative process!"
270
+ meta={[
271
+ {
272
+ icon: <Icon name="reply-outline" />,
273
+ value: 89,
274
+ label: "Comments",
275
+ },
276
+ {
277
+ icon: <Icon name="heart-outline" />,
278
+ value: 1240,
279
+ label: "Likes",
280
+ },
281
+ {
282
+ icon: <Icon name="repost-outline" />,
283
+ value: 203,
284
+ label: "Shares",
285
+ },
286
+ ]}
287
+ />
288
+ </Grid>
207
289
  </Grid>
208
290
  </NarrativeContainer>
291
+
209
292
  <NarrativeContainer>
210
293
  <NarrativeOrderedList
211
294
  orientation="horizontal"
package/src/css.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare module "*.css";
package/src/index.ts CHANGED
@@ -50,4 +50,15 @@ export {
50
50
 
51
51
  export { AIContainer, type TypeAIContainerProps } from "./AIContainer";
52
52
 
53
+ export {
54
+ NarrativeSocialMediaCard,
55
+ type TypeNarrativeSocialMediaCardProps,
56
+ type TypeNarrativeSocialMediaCardMeta,
57
+ } from "./NarrativeSocialMediaCard";
58
+
59
+ export {
60
+ NarrativeAttribution,
61
+ type TypeNarrativeAttributionProps,
62
+ } from "./NarrativeAttribution";
63
+
53
64
  export type { TypeNarrativeTone } from "./_internal/types";
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Seeds NarrativeAttribution 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-narrative-attribution">
10
+ * <img class="seeds-narrative-attribution-avatar" />
11
+ * <div class="seeds-narrative-attribution-social-icon">...</div>
12
+ * <span class="seeds-narrative-attribution-user-name">...</span>
13
+ * </div>
14
+ */
15
+
16
+ @layer components {
17
+
18
+ /* Main container — horizontal layout with avatar, social icon, and user name. */
19
+ .seeds-narrative-attribution {
20
+ box-sizing: border-box;
21
+ display: flex;
22
+ align-items: center;
23
+ gap: var(--space-300); /* 8px */;
24
+ font-family: var(--font-family);
25
+ }
26
+
27
+ /* Circular avatar image — fixed size with rounded corners. */
28
+ .seeds-narrative-attribution-avatar {
29
+ width: var(--space-450); /* 24px */
30
+ height: var(--space-450); /* 24px */
31
+ border-radius: var(--radius-circular); /* 50% */
32
+ object-fit: cover;
33
+ flex-shrink: 0;
34
+ }
35
+
36
+ /* Social network icon container — flexible sizing for partner logos. */
37
+ .seeds-narrative-attribution-social-icon {
38
+ flex-shrink: 0;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ }
43
+
44
+ /* User name text — bold headline text with ellipsis overflow. */
45
+ .seeds-narrative-attribution-user-name {
46
+ overflow: hidden;
47
+ color: var(--color-text-headline);
48
+ text-overflow: ellipsis;
49
+ font-family: var(--font-family);
50
+ font-size: var(--text-base-font-size, 14px);
51
+ font-style: normal;
52
+ font-weight: var(--font-weight-bold, 700);
53
+ line-height: var(--text-base-line-height, 24px);
54
+ }
55
+
56
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Seeds NarrativeSocialMediaCard 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-narrative-social-media-card">
10
+ * <div class="seeds-narrative-social-media-card-header">...</div>
11
+ * <div class="seeds-narrative-social-media-card-content">...</div>
12
+ * <div class="seeds-narrative-social-media-card-footer">...</div>
13
+ * </div>
14
+ */
15
+
16
+ @layer components {
17
+
18
+ /* Main card container — gray background with rounded corners and padding.
19
+ Uses Seeds tokens for spacing, colors, and radius to match the design system. */
20
+ .seeds-narrative-social-media-card {
21
+ box-sizing: border-box;
22
+ display: flex;
23
+ flex-direction: column;
24
+ align-items: flex-start;
25
+ max-width: 320px;
26
+ padding: var(--space-450); /* 24px */
27
+ border-radius: var(--radius-500); /* 6px */
28
+ background-color: var(--color-app-bg-base);
29
+ font-family: var(--font-family);
30
+ color: var(--color-text-body);
31
+ }
32
+
33
+ /* No background — transparent background for cards that blend seamlessly with the page. */
34
+ .seeds-narrative-social-media-card--no-background {
35
+ background-color: transparent;
36
+ }
37
+
38
+ /* Main content area — standard body text styling. */
39
+ .seeds-narrative-social-media-card-content {
40
+ flex: 1;
41
+ margin-top: var(--space-400); /* 16px */
42
+ font-family: var(--font-family);
43
+ font-size: 14px;
44
+ font-style: normal;
45
+ font-weight: 400;
46
+ line-height: 24px;
47
+ color: var(--color-text-body);
48
+ }
49
+
50
+ /* Footer section — horizontal layout for stats with equal spacing. */
51
+ .seeds-narrative-social-media-card-footer {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: var(--space-400); /* 16px */
55
+ margin-top: var(--space-400); /* 16px */;
56
+ }
57
+
58
+ /* Hide footer when empty (no metadata items). */
59
+ .seeds-narrative-social-media-card-footer:empty {
60
+ display: none;
61
+ }
62
+
63
+ /* Individual stat container — icon + value in horizontal layout. */
64
+ .seeds-narrative-social-media-card-stat {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: var(--space-200); /* 4px */
68
+ }
69
+
70
+ /* Stat value text — monospace font for numeric values. */
71
+ .seeds-narrative-social-media-card-stat-value {
72
+ font-family: var(--font-family-mono, ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace); font-size: 14px;
73
+ font-style: normal;
74
+ font-weight: 400;
75
+ line-height: 24px;
76
+ color: var(--color-text-body);
77
+ }
78
+
79
+ }