@sproutsocial/seeds-react-profile 0.3.36 → 0.3.39

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,40 @@
1
+ /**
2
+ * Tailwind/CSS-class implementation of ProfileToken.
3
+ * Uses Seeds ProfileToken CSS classes from profile-token.css.
4
+ *
5
+ * The composed Token component is not yet migrated, so its styling is applied
6
+ * via its className prop.
7
+ */
8
+
9
+ import { Token } from "@sproutsocial/seeds-react-token";
10
+ import { InlineProfile } from "./InlineProfile";
11
+ import type { ProfileTokenProps } from "./types";
12
+ import { cn } from "./cn";
13
+
14
+ /**
15
+ * A ProfileToken component that wraps InlineProfile in a Seeds Token component.
16
+ * This token is not closable and provides a compact way to display profile information inline.
17
+ *
18
+ * ProfileToken enforces design standards by only showing the profile name and network logo.
19
+ * It does not support avatars, secondary names (handles), or custom avatar sizes.
20
+ * Use InlineProfile directly if you need more flexibility.
21
+ */
22
+ export const ProfileTokenTailwind = ({
23
+ className,
24
+ onClick,
25
+ tokenProps,
26
+ ...props
27
+ }: ProfileTokenProps) => {
28
+ const { className: tokenClassName, ...restTokenProps } =
29
+ tokenProps ?? ({} as NonNullable<ProfileTokenProps["tokenProps"]>);
30
+ return (
31
+ <Token
32
+ className={cn("seeds-profile-token", className, tokenClassName)}
33
+ closeable={false}
34
+ onClick={onClick}
35
+ {...restTokenProps}
36
+ >
37
+ <InlineProfile size="small" {...props} />
38
+ </Token>
39
+ );
40
+ };
@@ -1,3 +1,4 @@
1
+ import styled from "styled-components";
1
2
  import { render, screen } from "@sproutsocial/seeds-react-testing-library";
2
3
  import { InlineProfile } from "../InlineProfile";
3
4
 
@@ -80,4 +81,40 @@ describe("InlineProfile", () => {
80
81
 
81
82
  expect(screen.getByLabelText("Verified account")).toBeInTheDocument();
82
83
  });
84
+
85
+ it("renders the Tailwind CSS-class implementation by default", () => {
86
+ const { container } = render(<InlineProfile name="John Doe" />);
87
+
88
+ // The default (no styled props / no styled() extension) path renders the
89
+ // pure CSS-class implementation, whose root carries the seeds-inline-profile class.
90
+ // eslint-disable-next-line testing-library/no-node-access
91
+ expect(container.firstChild).toHaveClass("seeds-inline-profile");
92
+ });
93
+
94
+ it("merges a consumer className onto the Tailwind root", () => {
95
+ const { container } = render(
96
+ <InlineProfile name="John Doe" className="custom-inline" />
97
+ );
98
+
99
+ // eslint-disable-next-line testing-library/no-node-access
100
+ expect(container.firstChild).toHaveClass("seeds-inline-profile");
101
+ // eslint-disable-next-line testing-library/no-node-access
102
+ expect(container.firstChild).toHaveClass("custom-inline");
103
+ });
104
+
105
+ it("falls back to the styled-components implementation when extended with styled()", () => {
106
+ const ExtendedInlineProfile = styled(InlineProfile)`
107
+ color: red;
108
+ `;
109
+
110
+ const { container } = render(<ExtendedInlineProfile name="John Doe" />);
111
+
112
+ // Content still renders through the legacy styled-components path.
113
+ expect(screen.getByText("John Doe")).toBeInTheDocument();
114
+ // The styled() extension injects a generated `sc-…` class name, which
115
+ // routes to the SC implementation — so the Tailwind class must NOT be
116
+ // present.
117
+ // eslint-disable-next-line testing-library/no-node-access
118
+ expect(container.firstChild).not.toHaveClass("seeds-inline-profile");
119
+ });
83
120
  });
@@ -146,6 +146,70 @@ describe("ProfileCard", () => {
146
146
  ).not.toBeInTheDocument();
147
147
  });
148
148
 
149
+ it("applies an explicit bannerColor as the header background (Tailwind path)", () => {
150
+ const { container } = render(
151
+ <ProfileCard {...defaultProps} bannerColor="#123456" />
152
+ );
153
+
154
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
155
+ const header = container.querySelector(".seeds-profile-card-header");
156
+ expect(header).not.toBeNull();
157
+ expect(header?.getAttribute("style")).toContain("#123456");
158
+ });
159
+
160
+ it("falls back to the network color variable when only partnerName is provided", () => {
161
+ // defaultProps.partnerName is "twitter" and no bannerColor is passed.
162
+ const { container } = render(<ProfileCard {...defaultProps} />);
163
+
164
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
165
+ const header = container.querySelector(".seeds-profile-card-header");
166
+ expect(header).not.toBeNull();
167
+ expect(header?.getAttribute("style")).toContain(
168
+ "var(--color-network-twitter)"
169
+ );
170
+ });
171
+
172
+ it("renders no header background when neither bannerColor nor a partner network is provided", () => {
173
+ const { partnerName, ...propsWithoutPartner } = defaultProps;
174
+ const { container } = render(<ProfileCard {...propsWithoutPartner} />);
175
+
176
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
177
+ const header = container.querySelector(".seeds-profile-card-header");
178
+ expect(header).not.toBeNull();
179
+ expect(header?.getAttribute("style") ?? "").not.toContain("background");
180
+ });
181
+
182
+ it("forwards cardProps.className to the card root while preserving the base class (Tailwind path)", () => {
183
+ const { container } = render(
184
+ <ProfileCard
185
+ {...defaultProps}
186
+ cardProps={{ role: "presentation", className: "p-300" }}
187
+ />
188
+ );
189
+
190
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
191
+ const root = container.querySelector(".seeds-profile-card");
192
+ expect(root).not.toBeNull();
193
+ expect(root?.className).toContain("seeds-profile-card");
194
+ expect(root?.className).toContain("p-300");
195
+ });
196
+
197
+ it("routes to the styled-components path when cardProps carries a styled-system prop", () => {
198
+ const { container } = render(
199
+ <ProfileCard
200
+ {...defaultProps}
201
+ cardProps={{ role: "presentation", p: 400 }}
202
+ />
203
+ );
204
+
205
+ // The styled-components path renders the composed Card, not the Tailwind
206
+ // root, so the Tailwind-only class must be absent — confirming styled-system
207
+ // props passed via cardProps reach the SC path (backward compat).
208
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
209
+ const tailwindRoot = container.querySelector(".seeds-profile-card");
210
+ expect(tailwindRoot).toBeNull();
211
+ });
212
+
149
213
  it("renders with all features combined", () => {
150
214
  render(
151
215
  <ProfileCard
@@ -8,6 +8,15 @@ describe("ProfileToken", () => {
8
8
  expect(screen.getByText("John Doe")).toBeInTheDocument();
9
9
  });
10
10
 
11
+ it("renders the Tailwind CSS-class implementation by default", () => {
12
+ const { container } = render(
13
+ <ProfileToken name="John Doe" partnerName="twitter" />
14
+ );
15
+
16
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
17
+ expect(container.firstChild).toHaveClass("seeds-profile-token");
18
+ });
19
+
11
20
  it("applies custom className", () => {
12
21
  const { container } = render(
13
22
  <ProfileToken
@@ -17,6 +26,9 @@ describe("ProfileToken", () => {
17
26
  />
18
27
  );
19
28
 
29
+ // The Tailwind path merges the base token class with the consumer className.
30
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
31
+ expect(container.firstChild).toHaveClass("seeds-profile-token");
20
32
  // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
21
33
  expect(container.firstChild).toHaveClass("custom-token");
22
34
  });
package/src/cn.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Local class-name merge helper for the Tailwind/CSS-class implementations.
3
+ *
4
+ * Copied verbatim from seeds-react-button's ButtonTailwind.tsx — `cn` is NOT
5
+ * exported from @sproutsocial/seeds-react-utilities, so each migrated package
6
+ * keeps its own local copy.
7
+ */
8
+ export function cn(
9
+ ...inputs: (string | undefined | null | false | Record<string, boolean>)[]
10
+ ): string {
11
+ const classes: string[] = [];
12
+
13
+ for (const input of inputs) {
14
+ if (!input) continue;
15
+
16
+ if (typeof input === "string") {
17
+ classes.push(input);
18
+ } else if (typeof input === "object") {
19
+ for (const [key, value] of Object.entries(input)) {
20
+ if (value) {
21
+ classes.push(key);
22
+ }
23
+ }
24
+ }
25
+ }
26
+
27
+ return classes.join(" ");
28
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Seeds InlineProfile component classes
3
+ *
4
+ * Requires @sproutsocial/seeds-react-theme/dist/theme-all.css imported for
5
+ * CSS variable definitions and dark mode support.
6
+ */
7
+
8
+ @layer components {
9
+ .seeds-inline-profile {
10
+ display: inline-flex;
11
+ flex-direction: row;
12
+ align-items: center;
13
+ gap: var(--space-200);
14
+ max-width: 100%;
15
+ font-size: var(--font-size-200);
16
+ line-height: var(--line-height-200);
17
+ color: var(--color-text-subtext);
18
+ text-align: inherit;
19
+ }
20
+
21
+ .seeds-inline-profile-avatar {
22
+ display: inline-flex;
23
+ flex-shrink: 0;
24
+ }
25
+
26
+ .seeds-inline-profile-content {
27
+ display: inline-flex;
28
+ align-items: center;
29
+ gap: var(--space-200);
30
+ min-width: 0;
31
+ flex: 1;
32
+ }
33
+
34
+ .seeds-inline-profile-network {
35
+ display: inline-flex;
36
+ align-items: center;
37
+ flex-shrink: 0;
38
+ }
39
+
40
+ .seeds-inline-profile-text {
41
+ display: inline-flex;
42
+ flex-direction: row;
43
+ align-items: center;
44
+ gap: var(--space-200);
45
+ min-width: 0;
46
+ flex: 1;
47
+ }
48
+
49
+ .seeds-inline-profile-name {
50
+ display: inline-block;
51
+ font-size: var(--font-size-200);
52
+ line-height: var(--line-height-200);
53
+ font-family: var(--font-family);
54
+ font-weight: var(--font-weight-semibold);
55
+ color: var(--color-text-body);
56
+ max-width: 100%;
57
+ overflow: hidden;
58
+ text-overflow: ellipsis;
59
+ white-space: nowrap;
60
+ }
61
+
62
+ .seeds-inline-profile-handle {
63
+ display: inline-block;
64
+ font-size: var(--font-size-200);
65
+ line-height: var(--line-height-200);
66
+ font-family: var(--font-family);
67
+ color: var(--color-text-subtext);
68
+ max-width: 100%;
69
+ overflow: hidden;
70
+ text-overflow: ellipsis;
71
+ white-space: nowrap;
72
+ }
73
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Seeds ProfileCard component classes
3
+ *
4
+ * Requires @sproutsocial/seeds-react-theme/dist/theme-all.css imported for
5
+ * CSS variable definitions and dark mode support.
6
+ *
7
+ * The header background color is dynamic (consumer bannerColor or the network
8
+ * color for the partner) and is supplied via the
9
+ * --seeds-profile-card-banner-bg custom property set as an inline style on the
10
+ * header element; the background declaration below consumes it.
11
+ */
12
+
13
+ @layer components {
14
+ .seeds-profile-card {
15
+ max-width: 300px;
16
+ border-radius: var(--radius-500);
17
+ background-color: var(--color-container-bg-base);
18
+ border: 1px solid var(--color-container-border-base);
19
+ }
20
+
21
+ .seeds-profile-card-header {
22
+ display: flex;
23
+ flex-direction: row;
24
+ position: relative;
25
+ height: 80px;
26
+ padding: var(--space-400) var(--space-400) 0 var(--space-400);
27
+ margin-bottom: 0;
28
+ color: var(--color-text-inverse);
29
+ background: var(--seeds-profile-card-banner-bg, transparent);
30
+ border-radius: var(--radius-500) var(--radius-500) 0 0;
31
+ }
32
+
33
+ .seeds-profile-card-footer {
34
+ border-top: 1px solid var(--color-container-border-base);
35
+ border-radius: 0 0 var(--radius-500) var(--radius-500);
36
+ padding: var(--space-400);
37
+ }
38
+
39
+ .seeds-profile-card-banner-content {
40
+ display: flex;
41
+ justify-content: flex-end;
42
+ width: 100%;
43
+ }
44
+
45
+ .seeds-profile-card-actions {
46
+ display: flex;
47
+ justify-content: flex-end;
48
+ gap: var(--space-100);
49
+ padding: 0 var(--space-300);
50
+ }
51
+
52
+ .seeds-profile-card-actions:empty {
53
+ min-height: 44px;
54
+ }
55
+
56
+ .seeds-profile-card-content {
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: var(--space-300);
60
+ padding: 0 var(--space-400) var(--space-400);
61
+ }
62
+
63
+ .seeds-profile-card-name-section {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: var(--space-200);
67
+ }
68
+
69
+ .seeds-profile-card-info-row {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: var(--space-300);
73
+ }
74
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Seeds ProfileToken component classes
3
+ *
4
+ * Requires @sproutsocial/seeds-react-theme/dist/theme-all.css imported for
5
+ * CSS variable definitions and dark mode support.
6
+ */
7
+
8
+ @layer components {
9
+ /*
10
+ * The rendered element carries both .seeds-token (from Token) and
11
+ * .seeds-profile-token at equal specificity, so the tighter profile padding
12
+ * would otherwise depend on CSS concatenation order. The compound selector
13
+ * raises specificity so this override deterministically wins over .seeds-token.
14
+ */
15
+ .seeds-token.seeds-profile-token {
16
+ box-sizing: border-box;
17
+ display: inline-flex;
18
+ max-width: 100%;
19
+ vertical-align: middle;
20
+ padding: var(--space-100) var(--space-200);
21
+ margin: var(--space-100) 0;
22
+ }
23
+ }