@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.
- package/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +46 -0
- package/dist/ai-container.css +39 -0
- package/dist/esm/index.js +217 -91
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +232 -14
- package/dist/index.d.ts +232 -14
- package/dist/index.js +224 -93
- package/dist/index.js.map +1 -1
- package/dist/metric-highlight.css +17 -27
- package/dist/narrative-data-callout.css +103 -0
- package/dist/narrative-divider.css +27 -0
- package/dist/narrative-headline.css +7 -0
- package/dist/narrative-ordered-list.css +112 -0
- package/dist/narrative-summary.css +126 -0
- package/dist/pull-quote.css +4 -25
- package/package.json +12 -5
- package/src/AIContainer/AIContainer.stories.tsx +54 -0
- package/src/AIContainer/AIContainer.tsx +35 -0
- package/src/AIContainer/AIContainerTypes.ts +14 -0
- package/src/AIContainer/__tests__/AIContainer.test.tsx +61 -0
- package/src/AIContainer/index.ts +2 -0
- package/src/EyebrowToken/EyebrowToken.tsx +1 -5
- package/src/MetricHighlight/MetricHighlight.stories.tsx +37 -14
- package/src/MetricHighlight/MetricHighlight.tsx +19 -99
- package/src/MetricHighlight/MetricHighlightTypes.ts +6 -9
- package/src/MetricHighlight/__tests__/MetricHighlight.test.tsx +13 -48
- package/src/NarrativeDataCallout/NarrativeDataCallout.stories.tsx +71 -0
- package/src/NarrativeDataCallout/NarrativeDataCallout.tsx +50 -0
- package/src/NarrativeDataCallout/NarrativeDataCalloutTypes.ts +18 -0
- package/src/NarrativeDataCallout/__tests__/NarrativeDataCallout.test.tsx +69 -0
- package/src/NarrativeDataCallout/index.ts +2 -0
- package/src/NarrativeDivider/NarrativeDivider.stories.tsx +33 -0
- package/src/NarrativeDivider/NarrativeDivider.tsx +30 -0
- package/src/NarrativeDivider/NarrativeDividerTypes.ts +4 -0
- package/src/NarrativeDivider/__tests__/NarrativeDivider.test.tsx +35 -0
- package/src/NarrativeDivider/index.ts +2 -0
- package/src/NarrativeHeadline/NarrativeHeadline.tsx +21 -12
- package/src/NarrativeHeadline/NarrativeHeadlineRoll.tsx +4 -1
- package/src/NarrativeHeadline/NarrativeHeadlineTypes.ts +9 -0
- package/src/NarrativeHeadline/__tests__/NarrativeHeadline.test.tsx +2 -7
- package/src/NarrativeHeadline/index.ts +1 -0
- package/src/NarrativeOrderedList/NarrativeOrderedList.stories.tsx +69 -0
- package/src/NarrativeOrderedList/NarrativeOrderedList.tsx +74 -0
- package/src/NarrativeOrderedList/NarrativeOrderedListTypes.ts +39 -0
- package/src/NarrativeOrderedList/__tests__/NarrativeOrderedList.test.tsx +89 -0
- package/src/NarrativeOrderedList/index.ts +6 -0
- package/src/NarrativeSummary/NarrativeSummary.stories.tsx +79 -0
- package/src/NarrativeSummary/NarrativeSummary.tsx +98 -0
- package/src/NarrativeSummary/NarrativeSummaryTypes.ts +59 -0
- package/src/NarrativeSummary/__tests__/NarrativeSummary.test.tsx +99 -0
- package/src/NarrativeSummary/index.ts +5 -0
- package/src/Playground/Playground.stories.tsx +174 -36
- package/src/PullQuote/PullQuote.stories.tsx +4 -1
- package/src/PullQuote/PullQuote.tsx +6 -3
- package/src/PullQuote/PullQuoteTypes.ts +1 -2
- package/src/ai-container.css +39 -0
- package/src/index.ts +25 -0
- package/src/metric-highlight.css +17 -27
- package/src/narrative-data-callout.css +103 -0
- package/src/narrative-divider.css +27 -0
- package/src/narrative-headline.css +7 -0
- package/src/narrative-ordered-list.css +112 -0
- package/src/narrative-summary.css +126 -0
- package/src/pull-quote.css +4 -25
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeds NarrativeDivider 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
|
+
* <hr class="seeds-narrative-divider" />
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
@layer components {
|
|
13
|
+
|
|
14
|
+
/* A full-width, 1px horizontal rule. The <hr> ships with user-agent margins
|
|
15
|
+
and a beveled border, so reset those and draw the line with a single
|
|
16
|
+
border-top using the container-border-base token (#dee1e1), which also
|
|
17
|
+
tracks dark mode. */
|
|
18
|
+
.seeds-narrative-divider {
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
width: 100%;
|
|
21
|
+
height: 0;
|
|
22
|
+
margin: 0;
|
|
23
|
+
border: 0;
|
|
24
|
+
border-top: 1px solid var(--color-container-border-base); /* #dee1e1 */
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
}
|
|
@@ -27,6 +27,13 @@
|
|
|
27
27
|
color: var(--color-text-headline);
|
|
28
28
|
}
|
|
29
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
|
+
|
|
30
37
|
/* Static gradient accent for a span of headline text ("highlighted word").
|
|
31
38
|
Paints the AI brand gradient onto the glyphs via background-clip. */
|
|
32
39
|
.seeds-narrative-headline-highlight {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeds NarrativeOrderedList 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-ordered-list">
|
|
10
|
+
* <ol class="seeds-narrative-ordered-list-track">
|
|
11
|
+
* <li class="seeds-narrative-ordered-list-item">…</li>
|
|
12
|
+
* </ol>
|
|
13
|
+
* </div>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
@layer components {
|
|
17
|
+
|
|
18
|
+
/* Establishes the query container. The track inside reacts to this element's
|
|
19
|
+
inline size — a container query can only style descendants of the container,
|
|
20
|
+
never the container element itself. */
|
|
21
|
+
.seeds-narrative-ordered-list {
|
|
22
|
+
container-type: inline-size;
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
font-family: var(--font-family);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* The list itself, and the default (stacked) layout. The horizontal modifier
|
|
28
|
+
only expands to a row once the container query below has room, so horizontal
|
|
29
|
+
collapses back to this stack when narrow. */
|
|
30
|
+
.seeds-narrative-ordered-list-track {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
gap: var(--space-400); /* 16px */
|
|
34
|
+
margin: 0;
|
|
35
|
+
padding: 0;
|
|
36
|
+
list-style: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* An item row: the ordinal beside the body, on its own rounded surface. */
|
|
40
|
+
.seeds-narrative-ordered-list-item {
|
|
41
|
+
display: flex;
|
|
42
|
+
gap: var(--space-450); /* 24px */
|
|
43
|
+
align-items: flex-start;
|
|
44
|
+
box-sizing: border-box;
|
|
45
|
+
min-width: 0;
|
|
46
|
+
padding: var(--space-400) var(--space-450); /* 16px 24px */
|
|
47
|
+
border-radius: var(--radius-800); /* 12px */
|
|
48
|
+
background-color: var(--color-app-bg-base);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* The ordinal (01, 02, …). No mono token exists yet, so fall back to a system
|
|
52
|
+
monospace stack — mirrors the value treatment in metric-highlight.css. */
|
|
53
|
+
.seeds-narrative-ordered-list-number {
|
|
54
|
+
margin: 0;
|
|
55
|
+
flex-shrink: 0;
|
|
56
|
+
font-family: var(--font-family-mono, ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace);
|
|
57
|
+
font-size: var(--font-size-700); /* 32px */
|
|
58
|
+
line-height: var(--line-height-700); /* 40px */
|
|
59
|
+
font-weight: var(--font-weight-bold);
|
|
60
|
+
color: var(--color-text-headline);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Title + description + optional action, stacked. */
|
|
64
|
+
.seeds-narrative-ordered-list-body {
|
|
65
|
+
flex: 1 1 auto;
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
gap: var(--space-200); /* 4px */
|
|
69
|
+
min-width: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Title. 16px / 28px — the 28px line-height has no exact type token (the scale
|
|
73
|
+
skips from 24px to 32px), so the design value is kept verbatim. Same
|
|
74
|
+
rationale as narrative-summary.css. */
|
|
75
|
+
.seeds-narrative-ordered-list-title {
|
|
76
|
+
margin: 0;
|
|
77
|
+
font-size: var(--font-size-300); /* 16px */
|
|
78
|
+
line-height: 28px; /* off-scale, see comment above */
|
|
79
|
+
font-weight: var(--font-weight-bold);
|
|
80
|
+
color: var(--color-text-body);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Description. 14px / 24px has no exact type token, so it is kept verbatim —
|
|
84
|
+
same rationale as the title above. */
|
|
85
|
+
.seeds-narrative-ordered-list-description {
|
|
86
|
+
margin: 0;
|
|
87
|
+
font-size: 14px; /* off-scale, see title above */
|
|
88
|
+
line-height: 24px;
|
|
89
|
+
font-weight: var(--font-weight-normal);
|
|
90
|
+
color: var(--color-text-body);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.seeds-narrative-ordered-list-action {
|
|
94
|
+
padding-top: var(--space-350); /* 12px */
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Expand the horizontal variant to a row of equal columns once the component is
|
|
98
|
+
wide enough. The threshold is on the component's own width (container query),
|
|
99
|
+
so a narrow card on a wide screen still stacks. 640px = Tailwind/Seeds sm
|
|
100
|
+
breakpoint, matching narrative-summary.css. */
|
|
101
|
+
@container (min-width: 640px) {
|
|
102
|
+
.seeds-narrative-ordered-list-horizontal {
|
|
103
|
+
flex-direction: row;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.seeds-narrative-ordered-list-horizontal > .seeds-narrative-ordered-list-item {
|
|
107
|
+
flex: 1 1 0;
|
|
108
|
+
min-width: 0;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/pull-quote.css
CHANGED
|
@@ -2,20 +2,17 @@
|
|
|
2
2
|
* Seeds PullQuote component classes.
|
|
3
3
|
* Use these instead of writing out individual Tailwind utility classes.
|
|
4
4
|
*
|
|
5
|
-
* Requires @sproutsocial/seeds-react-theme/dist/theme-all.css
|
|
6
|
-
* CSS variable definitions and
|
|
5
|
+
* Requires @sproutsocial/seeds-react-theme/dist/theme-all.css and
|
|
6
|
+
* ai-container.css imported for CSS variable definitions and surface styles.
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
9
|
-
* <figure class="seeds-pull-quote">…</figure> // subtle (default)
|
|
10
|
-
* <figure class="seeds-pull-quote seeds-pull-quote-vivid">…</figure>
|
|
9
|
+
* <figure class="seeds-pull-quote seeds-ai-container">…</figure> // subtle (default)
|
|
10
|
+
* <figure class="seeds-pull-quote seeds-ai-container seeds-ai-container-vivid seeds-pull-quote-vivid">…</figure>
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
@layer components {
|
|
14
14
|
|
|
15
15
|
.seeds-pull-quote {
|
|
16
|
-
position: relative;
|
|
17
|
-
overflow: hidden;
|
|
18
|
-
box-sizing: border-box;
|
|
19
16
|
width: 100%;
|
|
20
17
|
display: flex;
|
|
21
18
|
flex-direction: column;
|
|
@@ -24,24 +21,6 @@
|
|
|
24
21
|
/* 40px block / 48px inline — 48px is off the space scale, so derive it from
|
|
25
22
|
two tokens (40 + 8) rather than hardcoding an off-scale value. */
|
|
26
23
|
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
24
|
}
|
|
46
25
|
|
|
47
26
|
/* The accented bar holding the quote and attribution. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sproutsocial/seeds-react-narrative-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|
|
@@ -17,10 +17,15 @@
|
|
|
17
17
|
"./dist/narrative-headline.css": "./dist/narrative-headline.css",
|
|
18
18
|
"./dist/pull-quote.css": "./dist/pull-quote.css",
|
|
19
19
|
"./dist/metric-highlight.css": "./dist/metric-highlight.css",
|
|
20
|
-
"./dist/eyebrow-token.css": "./dist/eyebrow-token.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",
|
|
23
|
+
"./dist/narrative-ordered-list.css": "./dist/narrative-ordered-list.css",
|
|
24
|
+
"./dist/narrative-data-callout.css": "./dist/narrative-data-callout.css",
|
|
25
|
+
"./dist/ai-container.css": "./dist/ai-container.css"
|
|
21
26
|
},
|
|
22
27
|
"scripts": {
|
|
23
|
-
"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",
|
|
28
|
+
"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 && cp src/narrative-ordered-list.css dist/narrative-ordered-list.css && cp src/narrative-data-callout.css dist/narrative-data-callout.css && cp src/ai-container.css dist/ai-container.css",
|
|
24
29
|
"build:debug": "tsup --dts --metafile",
|
|
25
30
|
"dev": "tsup --watch --dts",
|
|
26
31
|
"clean": "rm -rf .turbo dist",
|
|
@@ -30,11 +35,13 @@
|
|
|
30
35
|
"test:watch": "jest --watch --coverage=false"
|
|
31
36
|
},
|
|
32
37
|
"dependencies": {
|
|
33
|
-
"@sproutsocial/seeds-react-theme": "^4.1.
|
|
38
|
+
"@sproutsocial/seeds-react-theme": "^4.1.1"
|
|
34
39
|
},
|
|
35
40
|
"devDependencies": {
|
|
36
41
|
"@sproutsocial/eslint-config-seeds": "*",
|
|
37
|
-
"@sproutsocial/seeds-react-
|
|
42
|
+
"@sproutsocial/seeds-react-button": "*",
|
|
43
|
+
"@sproutsocial/seeds-react-data-viz": "*",
|
|
44
|
+
"@sproutsocial/seeds-react-grid": "^0.2.6",
|
|
38
45
|
"@sproutsocial/seeds-react-text": "*",
|
|
39
46
|
"@sproutsocial/seeds-react-testing-library": "*",
|
|
40
47
|
"@sproutsocial/seeds-testing": "*",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import AIContainer from "./AIContainer";
|
|
4
|
+
import "../ai-container.css";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Narrative Kit/AIContainer",
|
|
8
|
+
component: AIContainer,
|
|
9
|
+
args: {
|
|
10
|
+
className: "w-80",
|
|
11
|
+
style: { padding: "24px" },
|
|
12
|
+
},
|
|
13
|
+
} satisfies Meta<typeof AIContainer>;
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof meta>;
|
|
17
|
+
|
|
18
|
+
const ShortContent = () => (
|
|
19
|
+
<p>
|
|
20
|
+
Engagement is up 24% this month, driven by a strong week on LinkedIn and a
|
|
21
|
+
viral reply thread.
|
|
22
|
+
</p>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const RichContent = () => (
|
|
26
|
+
<div className="grid gap-300">
|
|
27
|
+
<strong>AI-generated summary</strong>
|
|
28
|
+
<p>
|
|
29
|
+
Audience growth held steady while impressions climbed. Your top-performing
|
|
30
|
+
post reached 3x the usual engagement rate.
|
|
31
|
+
</p>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export const Subtle: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
appearance: "subtle",
|
|
38
|
+
children: <ShortContent />,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const Vivid: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
appearance: "vivid",
|
|
45
|
+
children: <ShortContent />,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const WithChildren: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
appearance: "subtle",
|
|
52
|
+
children: <RichContent />,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../_internal/cn";
|
|
3
|
+
import type { TypeAIContainerProps } from "./AIContainerTypes";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* AIContainer — a rounded surface primitive with the AI gradient background.
|
|
7
|
+
* Provides the outer shell (border-radius, shadow, overflow, gradient) without
|
|
8
|
+
* any padding. Used by PullQuote and available as a standalone container.
|
|
9
|
+
*
|
|
10
|
+
* - `subtle` — a light 20% gradient tint over the base surface with adaptive text color.
|
|
11
|
+
* - `vivid` — the full-saturation AI gradient with white text (always white).
|
|
12
|
+
*
|
|
13
|
+
* Styled via `ai-container.css` (Seeds CSS custom properties).
|
|
14
|
+
*/
|
|
15
|
+
const AIContainer = React.forwardRef<HTMLDivElement, TypeAIContainerProps>(
|
|
16
|
+
({ children, appearance = "subtle", className, ...props }, ref) => {
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
ref={ref}
|
|
20
|
+
className={cn(
|
|
21
|
+
"seeds-ai-container",
|
|
22
|
+
{ "seeds-ai-container-vivid": appearance === "vivid" },
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
AIContainer.displayName = "AIContainer";
|
|
34
|
+
|
|
35
|
+
export default AIContainer;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
|
|
3
|
+
export interface TypeAIContainerProps
|
|
4
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
/** Content rendered inside the container. */
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
/**
|
|
8
|
+
* Visual treatment of the AI gradient surface.
|
|
9
|
+
* - `subtle` — a light 20% gradient tint over the base surface with dark text.
|
|
10
|
+
* - `vivid` — the full-saturation AI gradient with white text (always white).
|
|
11
|
+
* @default "subtle"
|
|
12
|
+
*/
|
|
13
|
+
appearance?: "subtle" | "vivid";
|
|
14
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@sproutsocial/seeds-react-testing-library";
|
|
3
|
+
import AIContainer from "../AIContainer";
|
|
4
|
+
|
|
5
|
+
describe("AIContainer", () => {
|
|
6
|
+
it("renders its children", () => {
|
|
7
|
+
render(<AIContainer>Summary text</AIContainer>);
|
|
8
|
+
|
|
9
|
+
expect(screen.getByText("Summary text")).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("renders as a div", () => {
|
|
13
|
+
const { container } = render(<AIContainer>content</AIContainer>);
|
|
14
|
+
|
|
15
|
+
expect(container.firstChild?.nodeName).toBe("DIV");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("applies the base class", () => {
|
|
19
|
+
const { container } = render(<AIContainer>content</AIContainer>);
|
|
20
|
+
|
|
21
|
+
expect(container.querySelector(".seeds-ai-container")).toBeInTheDocument();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("does not apply the vivid modifier by default", () => {
|
|
25
|
+
const { container } = render(<AIContainer>content</AIContainer>);
|
|
26
|
+
|
|
27
|
+
expect(
|
|
28
|
+
container.querySelector(".seeds-ai-container-vivid")
|
|
29
|
+
).not.toBeInTheDocument();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("applies the vivid modifier only for the vivid appearance", () => {
|
|
33
|
+
const { container, rerender } = render(<AIContainer>content</AIContainer>);
|
|
34
|
+
expect(
|
|
35
|
+
container.querySelector(".seeds-ai-container-vivid")
|
|
36
|
+
).not.toBeInTheDocument();
|
|
37
|
+
|
|
38
|
+
rerender(<AIContainer appearance="vivid">content</AIContainer>);
|
|
39
|
+
expect(
|
|
40
|
+
container.querySelector(".seeds-ai-container-vivid")
|
|
41
|
+
).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("merges a consumer className with the base class", () => {
|
|
45
|
+
const { container } = render(
|
|
46
|
+
<AIContainer className="custom">content</AIContainer>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const root = container.querySelector(".seeds-ai-container");
|
|
50
|
+
expect(root).toBeInTheDocument();
|
|
51
|
+
expect(root).toHaveClass("custom");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("forwards its ref to the underlying div element", () => {
|
|
55
|
+
const ref = React.createRef<HTMLDivElement>();
|
|
56
|
+
render(<AIContainer ref={ref}>content</AIContainer>);
|
|
57
|
+
|
|
58
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
59
|
+
expect(ref.current?.tagName).toBe("DIV");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -14,11 +14,7 @@ import type { TypeEyebrowTokenProps } from "./EyebrowTokenTypes";
|
|
|
14
14
|
*/
|
|
15
15
|
const EyebrowToken = React.forwardRef<HTMLSpanElement, TypeEyebrowTokenProps>(
|
|
16
16
|
({ children, className, ...props }, ref) => (
|
|
17
|
-
<span
|
|
18
|
-
ref={ref}
|
|
19
|
-
className={cn("seeds-eyebrow-token", className)}
|
|
20
|
-
{...props}
|
|
21
|
-
>
|
|
17
|
+
<span ref={ref} className={cn("seeds-eyebrow-token", className)} {...props}>
|
|
22
18
|
{children}
|
|
23
19
|
</span>
|
|
24
20
|
)
|