@sproutsocial/seeds-react-narrative-kit 0.3.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 +27 -0
- package/dist/ai-container.css +39 -0
- package/dist/esm/index.js +98 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +123 -4
- package/dist/index.d.ts +123 -4
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -1
- package/dist/narrative-data-callout.css +103 -0
- package/dist/narrative-ordered-list.css +112 -0
- package/dist/pull-quote.css +4 -25
- package/package.json +8 -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/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/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/Playground/Playground.stories.tsx +93 -5
- package/src/PullQuote/PullQuote.stories.tsx +1 -0
- package/src/PullQuote/PullQuote.tsx +6 -3
- package/src/ai-container.css +39 -0
- package/src/index.ts +14 -0
- package/src/narrative-data-callout.css +103 -0
- package/src/narrative-ordered-list.css +112 -0
- package/src/pull-quote.css +4 -25
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@sproutsocial/seeds-react-testing-library";
|
|
3
|
+
import NarrativeOrderedList from "../NarrativeOrderedList";
|
|
4
|
+
import type { TypeNarrativeOrderedListItem } from "../NarrativeOrderedListTypes";
|
|
5
|
+
|
|
6
|
+
const items: TypeNarrativeOrderedListItem[] = [
|
|
7
|
+
{ title: "First action", description: "Do the first thing." },
|
|
8
|
+
{ title: "Second action", description: "Do the second thing." },
|
|
9
|
+
{ title: "Third action", description: "Do the third thing." },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
describe("NarrativeOrderedList", () => {
|
|
13
|
+
it("renders an ordered list with one item per entry", () => {
|
|
14
|
+
const { container } = render(<NarrativeOrderedList items={items} />);
|
|
15
|
+
|
|
16
|
+
expect(
|
|
17
|
+
container.querySelector("ol.seeds-narrative-ordered-list-track")
|
|
18
|
+
).toBeInTheDocument();
|
|
19
|
+
expect(screen.getAllByRole("listitem")).toHaveLength(3);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("renders each item's title and description", () => {
|
|
23
|
+
render(<NarrativeOrderedList items={items} />);
|
|
24
|
+
|
|
25
|
+
expect(screen.getByText("First action")).toBeInTheDocument();
|
|
26
|
+
expect(screen.getByText("Do the first thing.")).toBeInTheDocument();
|
|
27
|
+
expect(screen.getByText("Third action")).toBeInTheDocument();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("auto-numbers items with zero-padded ordinals", () => {
|
|
31
|
+
render(<NarrativeOrderedList items={items} />);
|
|
32
|
+
|
|
33
|
+
expect(screen.getByText("01")).toBeInTheDocument();
|
|
34
|
+
expect(screen.getByText("02")).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByText("03")).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("omits the description when it is not provided", () => {
|
|
39
|
+
const { container } = render(
|
|
40
|
+
<NarrativeOrderedList items={[{ title: "No description" }]} />
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
expect(screen.getByText("No description")).toBeInTheDocument();
|
|
44
|
+
expect(
|
|
45
|
+
container.querySelector(".seeds-narrative-ordered-list-description")
|
|
46
|
+
).not.toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("renders the action only when provided", () => {
|
|
50
|
+
const { rerender } = render(<NarrativeOrderedList items={items} />);
|
|
51
|
+
expect(
|
|
52
|
+
screen.queryByRole("button", { name: "Action" })
|
|
53
|
+
).not.toBeInTheDocument();
|
|
54
|
+
|
|
55
|
+
rerender(
|
|
56
|
+
<NarrativeOrderedList
|
|
57
|
+
items={[{ title: "With action", action: <button>Action</button> }]}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
expect(screen.getByRole("button", { name: "Action" })).toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("applies the horizontal modifier class", () => {
|
|
64
|
+
const { container } = render(
|
|
65
|
+
<NarrativeOrderedList items={items} orientation="horizontal" />
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(
|
|
69
|
+
container.querySelector(".seeds-narrative-ordered-list-horizontal")
|
|
70
|
+
).toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("merges a consumer className with the base class", () => {
|
|
74
|
+
const { container } = render(
|
|
75
|
+
<NarrativeOrderedList items={items} className="custom" />
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const root = container.querySelector(".seeds-narrative-ordered-list");
|
|
79
|
+
expect(root).toBeInTheDocument();
|
|
80
|
+
expect(root).toHaveClass("custom");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("forwards its ref to the underlying container element", () => {
|
|
84
|
+
const ref = React.createRef<HTMLDivElement>();
|
|
85
|
+
render(<NarrativeOrderedList items={items} ref={ref} />);
|
|
86
|
+
|
|
87
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -10,9 +10,12 @@ import MetricHighlight from "../MetricHighlight/MetricHighlight";
|
|
|
10
10
|
import EyebrowToken from "../EyebrowToken/EyebrowToken";
|
|
11
11
|
import NarrativeDivider from "../NarrativeDivider/NarrativeDivider";
|
|
12
12
|
import NarrativeSummary from "../NarrativeSummary/NarrativeSummary";
|
|
13
|
+
import NarrativeOrderedList from "../NarrativeOrderedList/NarrativeOrderedList";
|
|
14
|
+
import NarrativeDataCallout from "../NarrativeDataCallout/NarrativeDataCallout";
|
|
13
15
|
import Button from "@sproutsocial/seeds-react-button";
|
|
14
16
|
import Text from "@sproutsocial/seeds-react-text";
|
|
15
17
|
import { SparklineChart } from "@sproutsocial/seeds-react-data-viz/sparkline";
|
|
18
|
+
import { DonutChart } from "@sproutsocial/seeds-react-data-viz/donut";
|
|
16
19
|
import "../narrative-container.css";
|
|
17
20
|
import "../narrative-headline.css";
|
|
18
21
|
import "../pull-quote.css";
|
|
@@ -20,12 +23,14 @@ import "../metric-highlight.css";
|
|
|
20
23
|
import "../eyebrow-token.css";
|
|
21
24
|
import "../narrative-divider.css";
|
|
22
25
|
import "../narrative-summary.css";
|
|
26
|
+
import "../narrative-ordered-list.css";
|
|
27
|
+
import "../narrative-data-callout.css";
|
|
23
28
|
import "@sproutsocial/seeds-react-button/dist/button.css";
|
|
24
29
|
import "@sproutsocial/seeds-react-data-viz/sparkline.css";
|
|
25
30
|
|
|
26
|
-
const spark = (data: number[]) => (
|
|
31
|
+
const spark = (data: number[], variant: "line" | "bar" = "line") => (
|
|
27
32
|
<SparklineChart
|
|
28
|
-
variant=
|
|
33
|
+
variant={variant}
|
|
29
34
|
description="Trend"
|
|
30
35
|
data={data}
|
|
31
36
|
className="h-full w-full"
|
|
@@ -86,14 +91,22 @@ const Brief = () => (
|
|
|
86
91
|
<MetricHighlight
|
|
87
92
|
label="Engagement rate"
|
|
88
93
|
value="6.4%"
|
|
89
|
-
trend={{
|
|
94
|
+
trend={{
|
|
95
|
+
direction: "up",
|
|
96
|
+
value: "240%",
|
|
97
|
+
"aria-label": "up 240%",
|
|
98
|
+
}}
|
|
90
99
|
sparkline={spark([3, 5, 4, 7, 6, 9])}
|
|
91
100
|
/>
|
|
92
101
|
<MetricHighlight
|
|
93
102
|
label="Response time"
|
|
94
103
|
value="2h"
|
|
95
|
-
trend={{
|
|
96
|
-
|
|
104
|
+
trend={{
|
|
105
|
+
direction: "down",
|
|
106
|
+
value: "12%",
|
|
107
|
+
"aria-label": "down 12%",
|
|
108
|
+
}}
|
|
109
|
+
sparkline={spark([18, 16, 17, 13, 14, 10], "bar")}
|
|
97
110
|
/>
|
|
98
111
|
</Grid>
|
|
99
112
|
|
|
@@ -120,6 +133,37 @@ const Brief = () => (
|
|
|
120
133
|
|
|
121
134
|
<NarrativeDivider />
|
|
122
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
|
+
|
|
123
167
|
{/* Narrative summary (3-column) — below the pull quotes --------- */}
|
|
124
168
|
<NarrativeSummary
|
|
125
169
|
eyebrow="Quarterly review"
|
|
@@ -138,8 +182,52 @@ const Brief = () => (
|
|
|
138
182
|
quote="This rollout cut our average response time nearly in half."
|
|
139
183
|
attribution={<Attribution name="Priya Nair" />}
|
|
140
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>
|
|
141
207
|
</Grid>
|
|
142
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>
|
|
143
231
|
</div>
|
|
144
232
|
</div>
|
|
145
233
|
);
|
|
@@ -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
|
|
12
|
-
* the classes through
|
|
13
|
-
* are done with
|
|
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
|
)}
|
|
@@ -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
|
@@ -36,4 +36,18 @@ export {
|
|
|
36
36
|
type TypeNarrativeSummaryLayout,
|
|
37
37
|
} from "./NarrativeSummary";
|
|
38
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
|
+
|
|
39
53
|
export type { TypeNarrativeTone } from "./_internal/types";
|
|
@@ -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,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
|
+
}
|
package/src/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. */
|