@sproutsocial/seeds-react-profile 0.3.35 → 0.3.38
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 +67 -11
- package/CHANGELOG.md +47 -0
- package/dist/esm/index.js +255 -23
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +4 -42
- package/dist/index.d.ts +4 -42
- package/dist/index.js +255 -23
- package/dist/index.js.map +1 -1
- package/dist/inline-profile.css +73 -0
- package/dist/profile-card.css +74 -0
- package/dist/profile-token.css +23 -0
- package/package.json +21 -10
- package/src/InlineProfile.stories.tsx +27 -0
- package/src/InlineProfile.tsx +1 -153
- package/src/InlineProfileHybrid.tsx +18 -0
- package/src/InlineProfileStyled.tsx +143 -0
- package/src/InlineProfileTailwind.tsx +81 -0
- package/src/ProfileCard.stories.tsx +27 -0
- package/src/ProfileCard.tsx +4 -186
- package/src/ProfileCardHybrid.tsx +25 -0
- package/src/ProfileCardStyled.tsx +188 -0
- package/src/ProfileCardTailwind.tsx +154 -0
- package/src/ProfileToken.stories.tsx +16 -0
- package/src/ProfileToken.tsx +4 -40
- package/src/ProfileTokenHybrid.tsx +18 -0
- package/src/ProfileTokenStyled.tsx +37 -0
- package/src/ProfileTokenTailwind.tsx +40 -0
- package/src/__tests__/InlineProfile.test.tsx +37 -0
- package/src/__tests__/ProfileCard.test.tsx +64 -0
- package/src/__tests__/ProfileToken.test.tsx +12 -0
- package/src/cn.ts +28 -0
- package/src/inline-profile.css +73 -0
- package/src/profile-card.css +74 -0
- package/src/profile-token.css +23 -0
|
@@ -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
|
+
}
|