@sproutsocial/seeds-react-profile 0.1.3 → 0.2.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 +10 -10
- package/CHANGELOG.md +44 -0
- package/dist/esm/index.js +289 -28
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +81 -15
- package/dist/index.d.ts +81 -15
- package/dist/index.js +284 -27
- package/dist/index.js.map +1 -1
- package/package.json +11 -8
- package/src/InlineProfile.stories.tsx +74 -15
- package/src/InlineProfile.tsx +107 -30
- package/src/ProfileCard.stories.tsx +264 -0
- package/src/ProfileCard.tsx +186 -0
- package/src/ProfileToken.stories.tsx +90 -32
- package/src/ProfileToken.tsx +19 -7
- package/src/VerifiedProfileIcon.tsx +64 -0
- package/src/__tests__/InlineProfile.test.tsx +19 -20
- package/src/__tests__/ProfileCard.test.tsx +182 -0
- package/src/__tests__/ProfileToken.test.tsx +2 -1
- package/src/index.ts +1 -0
- package/src/types.ts +95 -9
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@sproutsocial/seeds-react-testing-library";
|
|
3
|
+
import { ProfileCard } from "../ProfileCard";
|
|
4
|
+
|
|
5
|
+
describe("ProfileCard", () => {
|
|
6
|
+
const defaultProps = {
|
|
7
|
+
name: "John Doe",
|
|
8
|
+
secondaryName: "@johndoe",
|
|
9
|
+
partnerName: "twitter" as const,
|
|
10
|
+
avatarUrl: "https://example.com/avatar.jpg",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
it("renders with name and secondaryName", () => {
|
|
14
|
+
render(<ProfileCard {...defaultProps} />);
|
|
15
|
+
|
|
16
|
+
expect(screen.getByText("John Doe")).toBeInTheDocument();
|
|
17
|
+
expect(screen.getByText("@johndoe")).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("renders with subtext", () => {
|
|
21
|
+
render(<ProfileCard {...defaultProps} subtext="Software Engineer" />);
|
|
22
|
+
|
|
23
|
+
expect(screen.getByText("Software Engineer")).toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("does not render subtext when not provided", () => {
|
|
27
|
+
render(<ProfileCard {...defaultProps} />);
|
|
28
|
+
|
|
29
|
+
expect(screen.queryByText("Software Engineer")).not.toBeInTheDocument();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("renders network logo", () => {
|
|
33
|
+
render(<ProfileCard {...defaultProps} />);
|
|
34
|
+
|
|
35
|
+
expect(screen.getByDataQaLabel({ logo: "twitter" })).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByLabelText("twitter profile")).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("renders description", () => {
|
|
40
|
+
render(
|
|
41
|
+
<ProfileCard
|
|
42
|
+
{...defaultProps}
|
|
43
|
+
description="Building amazing things with code"
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(
|
|
48
|
+
screen.getByText("Building amazing things with code")
|
|
49
|
+
).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("renders profile URL link with secondaryName", () => {
|
|
53
|
+
render(
|
|
54
|
+
<ProfileCard {...defaultProps} profileUrl="https://twitter.com/johndoe" />
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const link = screen.getByRole("link");
|
|
58
|
+
expect(link).toHaveAttribute("href", "https://twitter.com/johndoe");
|
|
59
|
+
expect(screen.getByText("@johndoe")).toBeInTheDocument();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("does not render profile URL when secondaryName is not provided", () => {
|
|
63
|
+
const { secondaryName, ...propsWithoutSecondary } = defaultProps;
|
|
64
|
+
render(
|
|
65
|
+
<ProfileCard
|
|
66
|
+
{...propsWithoutSecondary}
|
|
67
|
+
profileUrl="https://twitter.com/johndoe"
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(screen.queryByRole("link")).not.toBeInTheDocument();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("renders secondaryName without link when profileUrl is not provided", () => {
|
|
75
|
+
render(<ProfileCard {...defaultProps} />);
|
|
76
|
+
|
|
77
|
+
expect(screen.getByText("@johndoe")).toBeInTheDocument();
|
|
78
|
+
expect(screen.queryByRole("link")).not.toBeInTheDocument();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("renders location with icon", () => {
|
|
82
|
+
render(<ProfileCard {...defaultProps} location="San Francisco, CA" />);
|
|
83
|
+
|
|
84
|
+
expect(screen.getByText("San Francisco, CA")).toBeInTheDocument();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("renders metadata array", () => {
|
|
88
|
+
render(
|
|
89
|
+
<ProfileCard
|
|
90
|
+
{...defaultProps}
|
|
91
|
+
metadata={["1.2K followers", "500 following"]}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(screen.getByText("1.2K followers")).toBeInTheDocument();
|
|
96
|
+
expect(screen.getByText("500 following")).toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("renders with verification badge when verified", () => {
|
|
100
|
+
render(<ProfileCard {...defaultProps} verificationType="verified" />);
|
|
101
|
+
|
|
102
|
+
expect(screen.getByLabelText("Verified account")).toBeInTheDocument();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("renders banner content when provided", () => {
|
|
106
|
+
render(
|
|
107
|
+
<ProfileCard
|
|
108
|
+
{...defaultProps}
|
|
109
|
+
bannerContent={<div>Custom banner content</div>}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(screen.getByText("Custom banner content")).toBeInTheDocument();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("renders profile actions when provided", () => {
|
|
117
|
+
render(
|
|
118
|
+
<ProfileCard
|
|
119
|
+
{...defaultProps}
|
|
120
|
+
profileActions={<button>Edit Profile</button>}
|
|
121
|
+
/>
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(
|
|
125
|
+
screen.getByRole("button", { name: "Edit Profile" })
|
|
126
|
+
).toBeInTheDocument();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("renders footer when provided", () => {
|
|
130
|
+
render(
|
|
131
|
+
<ProfileCard
|
|
132
|
+
{...defaultProps}
|
|
133
|
+
footer={<div>Custom footer content</div>}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(screen.getByText("Custom footer content")).toBeInTheDocument();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("renders without network logo when partnerName is not provided", () => {
|
|
141
|
+
const { partnerName, ...propsWithoutPartner } = defaultProps;
|
|
142
|
+
render(<ProfileCard {...propsWithoutPartner} />);
|
|
143
|
+
|
|
144
|
+
expect(
|
|
145
|
+
screen.queryByDataQaLabel({ logo: "twitter" })
|
|
146
|
+
).not.toBeInTheDocument();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("renders with all features combined", () => {
|
|
150
|
+
render(
|
|
151
|
+
<ProfileCard
|
|
152
|
+
{...defaultProps}
|
|
153
|
+
subtext="Lead Engineer"
|
|
154
|
+
description="Passionate about building great products"
|
|
155
|
+
profileUrl="https://twitter.com/johndoe"
|
|
156
|
+
location="Seattle, WA"
|
|
157
|
+
metadata={["5K followers", "1K following"]}
|
|
158
|
+
verificationType="blue_verified"
|
|
159
|
+
bannerContent={<div>Banner</div>}
|
|
160
|
+
profileActions={<button>Actions</button>}
|
|
161
|
+
footer={<div>Footer</div>}
|
|
162
|
+
/>
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
expect(screen.getByText("John Doe")).toBeInTheDocument();
|
|
166
|
+
expect(screen.getByText("Lead Engineer")).toBeInTheDocument();
|
|
167
|
+
expect(
|
|
168
|
+
screen.getByText("Passionate about building great products")
|
|
169
|
+
).toBeInTheDocument();
|
|
170
|
+
expect(screen.getByText("@johndoe")).toBeInTheDocument();
|
|
171
|
+
expect(screen.getByRole("link")).toHaveAttribute(
|
|
172
|
+
"href",
|
|
173
|
+
"https://twitter.com/johndoe"
|
|
174
|
+
);
|
|
175
|
+
expect(screen.getByText("Seattle, WA")).toBeInTheDocument();
|
|
176
|
+
expect(screen.getByText("5K followers")).toBeInTheDocument();
|
|
177
|
+
expect(screen.getByLabelText("Verified account")).toBeInTheDocument();
|
|
178
|
+
expect(screen.getByText("Banner")).toBeInTheDocument();
|
|
179
|
+
expect(screen.getByRole("button", { name: "Actions" })).toBeInTheDocument();
|
|
180
|
+
expect(screen.getByText("Footer")).toBeInTheDocument();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -7,7 +7,8 @@ describe("ProfileToken", () => {
|
|
|
7
7
|
name: "John Doe",
|
|
8
8
|
secondaryName: "@johndoe",
|
|
9
9
|
partnerName: "twitter" as const,
|
|
10
|
-
|
|
10
|
+
avatarUrl: "https://example.com/avatar.jpg",
|
|
11
|
+
verificationType: "verified" as const,
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
it("renders profile information in token format", () => {
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -6,21 +6,107 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { EnumLogoNamesWithoutVariants } from "@sproutsocial/seeds-partner-logos";
|
|
9
|
-
import type
|
|
9
|
+
import type { TypeTokenProps } from "@sproutsocial/seeds-react-token";
|
|
10
|
+
import type { TypeCardProps } from "@sproutsocial/seeds-react-card";
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Social network types supported by the Profile component
|
|
14
|
+
* This is a subset of EnumLogoNamesWithoutVariants that only includes actual social networks
|
|
15
|
+
*/
|
|
16
|
+
export type TypeProfileNetwork = Extract<
|
|
17
|
+
EnumLogoNamesWithoutVariants,
|
|
18
|
+
| "twitter"
|
|
19
|
+
| "facebook"
|
|
20
|
+
| "instagram"
|
|
21
|
+
| "linkedin"
|
|
22
|
+
| "youtube"
|
|
23
|
+
| "tiktok"
|
|
24
|
+
| "threads"
|
|
25
|
+
| "bluesky"
|
|
26
|
+
| "reddit"
|
|
27
|
+
| "pinterest"
|
|
28
|
+
| "whatsapp"
|
|
29
|
+
| "glassdoor"
|
|
30
|
+
| "trustpilot"
|
|
31
|
+
| "tumblr"
|
|
32
|
+
| "yelp"
|
|
33
|
+
| "x-twitter"
|
|
34
|
+
>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Profile verification types
|
|
38
|
+
*/
|
|
39
|
+
export type TypeProfileVerification =
|
|
40
|
+
| "blue_verified" // Blue checkmark (Facebook, Twitter, etc.)
|
|
41
|
+
| "gray_verified" // Gray checkmark (Facebook, etc.)
|
|
42
|
+
| "verified" // Standard verification
|
|
43
|
+
| "not_verified"; // No verification
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Base props interface shared between Profile and InlineProfile components
|
|
47
|
+
*/
|
|
48
|
+
export interface TypeBaseProfileProps {
|
|
49
|
+
/** The network of the profile */
|
|
50
|
+
partnerName?: TypeProfileNetwork;
|
|
51
|
+
/** A custom label to apply to the partner logo */
|
|
52
|
+
partnerLogoLabel?: string;
|
|
53
|
+
/** The display name of the profile */
|
|
12
54
|
name: string;
|
|
55
|
+
/** The handle or username of the profile */
|
|
13
56
|
secondaryName?: string;
|
|
57
|
+
/** The profile picture URL */
|
|
58
|
+
avatarUrl?: string;
|
|
59
|
+
/** The type of verification for the profile, if applicable */
|
|
60
|
+
verificationType?: TypeProfileVerification;
|
|
61
|
+
/** Additional CSS class name */
|
|
62
|
+
className?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Props interface for the InlineProfile component
|
|
67
|
+
*/
|
|
68
|
+
export interface TypeInlineProfileProps
|
|
69
|
+
extends Omit<TypeBaseProfileProps, "name"> {
|
|
70
|
+
/** The display name of the profile (optional for inline) */
|
|
71
|
+
name?: string;
|
|
72
|
+
/** Size variant for the inline profile */
|
|
73
|
+
size?: "small" | "medium" | "large";
|
|
74
|
+
/** The px size of the avatar, which has a default based on the size prop */
|
|
14
75
|
avatarSize?: string;
|
|
15
|
-
partnerName?: EnumLogoNamesWithoutVariants;
|
|
16
|
-
partnerLogoLabel?: string;
|
|
17
|
-
img?: string;
|
|
18
|
-
children?: React.ReactNode | React.ReactNode[];
|
|
19
76
|
}
|
|
20
77
|
|
|
21
78
|
export interface ProfileTokenProps extends TypeInlineProfileProps {
|
|
22
|
-
/**
|
|
23
|
-
|
|
79
|
+
/** Click handler for the entire inline profile */
|
|
80
|
+
onClick?: TypeTokenProps["onClick"];
|
|
24
81
|
/** Additional props to pass to the Token component */
|
|
25
|
-
tokenProps?: Omit<
|
|
82
|
+
tokenProps?: Omit<TypeTokenProps, "children">;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Main props interface for the Profile component
|
|
87
|
+
*/
|
|
88
|
+
export interface TypeProfileCardProps extends TypeBaseProfileProps {
|
|
89
|
+
/** Subtext for below the profile name */
|
|
90
|
+
subtext?: string;
|
|
91
|
+
/** Profile description or bio */
|
|
92
|
+
description?: string;
|
|
93
|
+
/** External profile URL (e.g., "https://twitter.com/username"), only displayed if secondaryName is provided */
|
|
94
|
+
profileUrl?: string;
|
|
95
|
+
/** Location text (e.g., "San Francisco, CA") */
|
|
96
|
+
location?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Background color for the banner header.
|
|
99
|
+
* If not provided, will use theme.colors.network[partnerName] when partnerName is available.
|
|
100
|
+
*/
|
|
101
|
+
bannerColor?: string;
|
|
102
|
+
/** Generic content slot for banners, alerts, or status messages */
|
|
103
|
+
bannerContent?: React.ReactNode;
|
|
104
|
+
/** Array of metadata strings (e.g., ["1.2K followers", "500 following"]) */
|
|
105
|
+
metadata?: string[];
|
|
106
|
+
/** Action buttons or overflow menus for profile operations. Rendered in order provided, right-aligned. */
|
|
107
|
+
profileActions?: React.ReactNode;
|
|
108
|
+
/** Custom footer content */
|
|
109
|
+
footer?: React.ReactNode;
|
|
110
|
+
/** Props to pass down to the Card */
|
|
111
|
+
cardProps?: TypeCardProps;
|
|
26
112
|
}
|