@sproutsocial/seeds-react-profile 0.1.3 → 0.3.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 +52 -0
- package/dist/esm/index.js +293 -28
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +87 -17
- package/dist/index.d.ts +87 -17
- package/dist/index.js +288 -27
- package/dist/index.js.map +1 -1
- package/package.json +11 -8
- package/src/InlineProfile.stories.tsx +107 -15
- package/src/InlineProfile.tsx +108 -29
- package/src/ProfileCard.stories.tsx +264 -0
- package/src/ProfileCard.tsx +186 -0
- package/src/ProfileToken.stories.tsx +60 -102
- package/src/ProfileToken.tsx +28 -8
- package/src/VerifiedProfileIcon.tsx +64 -0
- package/src/__tests__/InlineProfile.test.tsx +23 -23
- package/src/__tests__/ProfileCard.test.tsx +182 -0
- package/src/__tests__/ProfileToken.test.tsx +14 -13
- package/src/index.ts +1 -0
- package/src/types.ts +100 -10
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import styled from "styled-components";
|
|
2
|
+
import { PartnerLogo } from "@sproutsocial/seeds-react-partner-logo";
|
|
3
|
+
import { Avatar } from "@sproutsocial/seeds-react-avatar";
|
|
4
|
+
import {
|
|
5
|
+
Card,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardFooter,
|
|
9
|
+
} from "@sproutsocial/seeds-react-card";
|
|
10
|
+
import { Icon } from "@sproutsocial/seeds-react-icon";
|
|
11
|
+
import { Link } from "@sproutsocial/seeds-react-link";
|
|
12
|
+
import { Text } from "@sproutsocial/seeds-react-text";
|
|
13
|
+
import type { TypeProfileCardProps } from "./types";
|
|
14
|
+
import { VerifiedProfileIcon } from "./VerifiedProfileIcon";
|
|
15
|
+
|
|
16
|
+
// Styled Components
|
|
17
|
+
const StyledProfileCard = styled(Card)`
|
|
18
|
+
max-width: 300px;
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const StyledCardHeader = styled(CardHeader)<{
|
|
22
|
+
$bannerColor?: string;
|
|
23
|
+
$partnerName?: string;
|
|
24
|
+
}>`
|
|
25
|
+
position: relative;
|
|
26
|
+
height: 80px;
|
|
27
|
+
padding: ${(props) => props.theme.space[400]}
|
|
28
|
+
${(props) => props.theme.space[400]} 0 ${(props) => props.theme.space[400]};
|
|
29
|
+
margin-bottom: 0;
|
|
30
|
+
color: ${(props) => props.theme.colors.text.inverse};
|
|
31
|
+
border-radius: ${(props) => props.theme.radii[500]}
|
|
32
|
+
${(props) => props.theme.radii[500]} 0 0;
|
|
33
|
+
${(props) => {
|
|
34
|
+
// Use explicit bannerColor if provided
|
|
35
|
+
if (props.$bannerColor) {
|
|
36
|
+
return `background: ${props.$bannerColor};`;
|
|
37
|
+
}
|
|
38
|
+
// Fall back to theme.colors.network[partnerName] if partnerName is available
|
|
39
|
+
if (props.$partnerName && props.theme.colors.network) {
|
|
40
|
+
const networkColor =
|
|
41
|
+
props.theme.colors.network[
|
|
42
|
+
props.$partnerName as keyof typeof props.theme.colors.network
|
|
43
|
+
];
|
|
44
|
+
return networkColor ? `background: ${networkColor};` : "";
|
|
45
|
+
}
|
|
46
|
+
return "";
|
|
47
|
+
}}
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
const BannerContent = styled.div`
|
|
51
|
+
display: flex;
|
|
52
|
+
justify-content: flex-end;
|
|
53
|
+
width: 100%;
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
const ActionsSection = styled.div`
|
|
57
|
+
display: flex;
|
|
58
|
+
justify-content: flex-end;
|
|
59
|
+
gap: ${(props) => props.theme.space[100]};
|
|
60
|
+
padding: 0 ${(props) => props.theme.space[300]};
|
|
61
|
+
|
|
62
|
+
&:empty {
|
|
63
|
+
min-height: 44px;
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const StyledCardContent = styled(CardContent)`
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
gap: ${(props) => props.theme.space[300]};
|
|
71
|
+
padding: 0 ${(props) => props.theme.space[400]}
|
|
72
|
+
${(props) => props.theme.space[400]};
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
const ProfileNameSection = styled.div`
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: ${(props) => props.theme.space[200]};
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
const InfoRow = styled.div`
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
gap: ${(props) => props.theme.space[300]};
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A flexible profile component that can display user profiles in various formats
|
|
89
|
+
* across different social networks with consistent styling and behavior.
|
|
90
|
+
*/
|
|
91
|
+
export const ProfileCard = ({
|
|
92
|
+
partnerName,
|
|
93
|
+
name,
|
|
94
|
+
secondaryName,
|
|
95
|
+
avatarUrl,
|
|
96
|
+
subtext,
|
|
97
|
+
description,
|
|
98
|
+
verificationType,
|
|
99
|
+
profileUrl,
|
|
100
|
+
location,
|
|
101
|
+
bannerColor,
|
|
102
|
+
bannerContent,
|
|
103
|
+
metadata,
|
|
104
|
+
profileActions,
|
|
105
|
+
footer,
|
|
106
|
+
cardProps,
|
|
107
|
+
}: TypeProfileCardProps) => {
|
|
108
|
+
return (
|
|
109
|
+
<StyledProfileCard role="presentation" {...cardProps}>
|
|
110
|
+
<StyledCardHeader $bannerColor={bannerColor} $partnerName={partnerName}>
|
|
111
|
+
<Avatar src={avatarUrl} name={name} size="60px" mt={450} />
|
|
112
|
+
{bannerContent && <BannerContent>{bannerContent}</BannerContent>}
|
|
113
|
+
</StyledCardHeader>
|
|
114
|
+
|
|
115
|
+
<ActionsSection>{profileActions}</ActionsSection>
|
|
116
|
+
|
|
117
|
+
<StyledCardContent>
|
|
118
|
+
<ProfileNameSection>
|
|
119
|
+
{partnerName && (
|
|
120
|
+
<PartnerLogo
|
|
121
|
+
partnerName={partnerName}
|
|
122
|
+
aria-label={`${partnerName} profile`}
|
|
123
|
+
size="small"
|
|
124
|
+
/>
|
|
125
|
+
)}
|
|
126
|
+
<Text.SubHeadline as="h2">{name}</Text.SubHeadline>
|
|
127
|
+
</ProfileNameSection>
|
|
128
|
+
|
|
129
|
+
{subtext && (
|
|
130
|
+
<Text fontSize={200} color="text.subtext">
|
|
131
|
+
{subtext}
|
|
132
|
+
</Text>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
{secondaryName && (
|
|
136
|
+
<InfoRow>
|
|
137
|
+
{profileUrl ? (
|
|
138
|
+
<Link href={profileUrl} external>
|
|
139
|
+
<Text fontSize={200}>{secondaryName}</Text>
|
|
140
|
+
<Icon
|
|
141
|
+
name="arrow-right-up-outline"
|
|
142
|
+
aria-hidden
|
|
143
|
+
ml={100}
|
|
144
|
+
size="small"
|
|
145
|
+
/>
|
|
146
|
+
</Link>
|
|
147
|
+
) : (
|
|
148
|
+
<Text.SmallByline color="text.subtext">
|
|
149
|
+
{secondaryName}
|
|
150
|
+
</Text.SmallByline>
|
|
151
|
+
)}
|
|
152
|
+
{verificationType && (
|
|
153
|
+
<VerifiedProfileIcon
|
|
154
|
+
partnerName={partnerName}
|
|
155
|
+
verificationType={verificationType}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
158
|
+
</InfoRow>
|
|
159
|
+
)}
|
|
160
|
+
|
|
161
|
+
{description && <Text fontSize={200}>{description}</Text>}
|
|
162
|
+
|
|
163
|
+
{location && (
|
|
164
|
+
<InfoRow>
|
|
165
|
+
<Icon name="location-pin-outline" aria-hidden size="small" />
|
|
166
|
+
<Text fontSize={200}>{location}</Text>
|
|
167
|
+
</InfoRow>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{metadata && metadata.length > 0 && (
|
|
171
|
+
<InfoRow>
|
|
172
|
+
{metadata.map((info, index) => (
|
|
173
|
+
<Text fontSize={200} key={index}>
|
|
174
|
+
{info}
|
|
175
|
+
</Text>
|
|
176
|
+
))}
|
|
177
|
+
</InfoRow>
|
|
178
|
+
)}
|
|
179
|
+
</StyledCardContent>
|
|
180
|
+
|
|
181
|
+
{footer && <CardFooter>{footer}</CardFooter>}
|
|
182
|
+
</StyledProfileCard>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export default ProfileCard;
|
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
2
|
import { ProfileToken } from "./ProfileToken";
|
|
4
|
-
import { Icon } from "@sproutsocial/seeds-react-icon";
|
|
5
|
-
|
|
6
|
-
const avatarUrl =
|
|
7
|
-
"https://d672eyudr6aq1.cloudfront.net/avatar/cede1373e17c05542b1cc60f427067f2?s=30&d=404";
|
|
8
|
-
|
|
9
|
-
const VerificationBadge = () => (
|
|
10
|
-
<Icon name="check-solid" style={{ color: "#1da1f2" }} />
|
|
11
|
-
);
|
|
12
3
|
|
|
13
4
|
const meta: Meta<typeof ProfileToken> = {
|
|
14
5
|
title: "Components/Profile/ProfileToken",
|
|
@@ -17,7 +8,25 @@ const meta: Meta<typeof ProfileToken> = {
|
|
|
17
8
|
layout: "centered",
|
|
18
9
|
},
|
|
19
10
|
tags: ["autodocs"],
|
|
20
|
-
argTypes: {
|
|
11
|
+
argTypes: {
|
|
12
|
+
partnerName: {
|
|
13
|
+
control: { type: "select" },
|
|
14
|
+
options: [
|
|
15
|
+
"twitter",
|
|
16
|
+
"facebook",
|
|
17
|
+
"instagram",
|
|
18
|
+
"linkedin",
|
|
19
|
+
"youtube",
|
|
20
|
+
"tiktok",
|
|
21
|
+
"threads",
|
|
22
|
+
"bluesky",
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
verificationType: {
|
|
26
|
+
control: { type: "select" },
|
|
27
|
+
options: ["verified", "blue_verified", "gray_verified", "not_verified"],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
21
30
|
};
|
|
22
31
|
|
|
23
32
|
export default meta;
|
|
@@ -26,8 +35,6 @@ type Story = StoryObj<typeof meta>;
|
|
|
26
35
|
export const Default: Story = {
|
|
27
36
|
args: {
|
|
28
37
|
name: "John Doe",
|
|
29
|
-
secondaryName: "@johndoe",
|
|
30
|
-
img: avatarUrl,
|
|
31
38
|
partnerName: "twitter",
|
|
32
39
|
},
|
|
33
40
|
};
|
|
@@ -35,18 +42,14 @@ export const Default: Story = {
|
|
|
35
42
|
export const WithVerification: Story = {
|
|
36
43
|
args: {
|
|
37
44
|
name: "Elon Musk",
|
|
38
|
-
secondaryName: "@elonmusk",
|
|
39
|
-
img: avatarUrl,
|
|
40
45
|
partnerName: "twitter",
|
|
41
|
-
|
|
46
|
+
verificationType: "verified",
|
|
42
47
|
},
|
|
43
48
|
};
|
|
44
49
|
|
|
45
50
|
export const FacebookUser: Story = {
|
|
46
51
|
args: {
|
|
47
52
|
name: "Facebook User",
|
|
48
|
-
secondaryName: "/facebookuser",
|
|
49
|
-
img: avatarUrl,
|
|
50
53
|
partnerName: "facebook",
|
|
51
54
|
},
|
|
52
55
|
};
|
|
@@ -54,8 +57,6 @@ export const FacebookUser: Story = {
|
|
|
54
57
|
export const InstagramUser: Story = {
|
|
55
58
|
args: {
|
|
56
59
|
name: "Instagram User",
|
|
57
|
-
secondaryName: "@instagramuser",
|
|
58
|
-
img: avatarUrl,
|
|
59
60
|
partnerName: "instagram",
|
|
60
61
|
},
|
|
61
62
|
};
|
|
@@ -63,8 +64,6 @@ export const InstagramUser: Story = {
|
|
|
63
64
|
export const LinkedInUser: Story = {
|
|
64
65
|
args: {
|
|
65
66
|
name: "LinkedIn User",
|
|
66
|
-
secondaryName: "linkedinuser",
|
|
67
|
-
img: avatarUrl,
|
|
68
67
|
partnerName: "linkedin",
|
|
69
68
|
},
|
|
70
69
|
};
|
|
@@ -72,8 +71,6 @@ export const LinkedInUser: Story = {
|
|
|
72
71
|
export const YouTubeUser: Story = {
|
|
73
72
|
args: {
|
|
74
73
|
name: "YouTube Creator",
|
|
75
|
-
secondaryName: "@youtubecreator",
|
|
76
|
-
img: avatarUrl,
|
|
77
74
|
partnerName: "youtube",
|
|
78
75
|
},
|
|
79
76
|
};
|
|
@@ -81,8 +78,6 @@ export const YouTubeUser: Story = {
|
|
|
81
78
|
export const TikTokUser: Story = {
|
|
82
79
|
args: {
|
|
83
80
|
name: "TikTok Creator",
|
|
84
|
-
secondaryName: "@tiktokcreator",
|
|
85
|
-
img: avatarUrl,
|
|
86
81
|
partnerName: "tiktok",
|
|
87
82
|
},
|
|
88
83
|
};
|
|
@@ -90,8 +85,6 @@ export const TikTokUser: Story = {
|
|
|
90
85
|
export const ThreadsUser: Story = {
|
|
91
86
|
args: {
|
|
92
87
|
name: "Threads User",
|
|
93
|
-
secondaryName: "@threadsuser",
|
|
94
|
-
img: avatarUrl,
|
|
95
88
|
partnerName: "threads",
|
|
96
89
|
},
|
|
97
90
|
};
|
|
@@ -99,59 +92,27 @@ export const ThreadsUser: Story = {
|
|
|
99
92
|
export const BlueskyUser: Story = {
|
|
100
93
|
args: {
|
|
101
94
|
name: "Bluesky User",
|
|
102
|
-
secondaryName: "@blueskyuser",
|
|
103
|
-
img: avatarUrl,
|
|
104
95
|
partnerName: "bluesky",
|
|
105
96
|
},
|
|
106
97
|
};
|
|
107
98
|
|
|
108
|
-
export const
|
|
99
|
+
export const BlueVerifiedUser: Story = {
|
|
109
100
|
args: {
|
|
110
|
-
name: "
|
|
111
|
-
secondaryName: "@noavat,ar",
|
|
101
|
+
name: "Blue Verified User",
|
|
112
102
|
partnerName: "twitter",
|
|
103
|
+
verificationType: "blue_verified",
|
|
113
104
|
},
|
|
114
105
|
};
|
|
115
106
|
|
|
116
107
|
export const DifferentNetworks: Story = {
|
|
117
108
|
render: () => (
|
|
118
109
|
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
|
|
119
|
-
<ProfileToken
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
/>
|
|
125
|
-
<ProfileToken
|
|
126
|
-
name="Facebook User"
|
|
127
|
-
secondaryName="/facebookuser"
|
|
128
|
-
img={avatarUrl}
|
|
129
|
-
partnerName="facebook"
|
|
130
|
-
/>
|
|
131
|
-
<ProfileToken
|
|
132
|
-
name="Instagram User"
|
|
133
|
-
secondaryName="@instagramuser"
|
|
134
|
-
img={avatarUrl}
|
|
135
|
-
partnerName="instagram"
|
|
136
|
-
/>
|
|
137
|
-
<ProfileToken
|
|
138
|
-
name="LinkedIn User"
|
|
139
|
-
secondaryName="linkedinuser"
|
|
140
|
-
img={avatarUrl}
|
|
141
|
-
partnerName="linkedin"
|
|
142
|
-
/>
|
|
143
|
-
<ProfileToken
|
|
144
|
-
name="YouTube Creator"
|
|
145
|
-
secondaryName="@youtubecreator"
|
|
146
|
-
img={avatarUrl}
|
|
147
|
-
partnerName="youtube"
|
|
148
|
-
/>
|
|
149
|
-
<ProfileToken
|
|
150
|
-
name="TikTok Creator"
|
|
151
|
-
secondaryName="@tiktokcreator"
|
|
152
|
-
img={avatarUrl}
|
|
153
|
-
partnerName="tiktok"
|
|
154
|
-
/>
|
|
110
|
+
<ProfileToken name="Twitter User" partnerName="twitter" />
|
|
111
|
+
<ProfileToken name="Facebook User" partnerName="facebook" />
|
|
112
|
+
<ProfileToken name="Instagram User" partnerName="instagram" />
|
|
113
|
+
<ProfileToken name="LinkedIn User" partnerName="linkedin" />
|
|
114
|
+
<ProfileToken name="YouTube Creator" partnerName="youtube" />
|
|
115
|
+
<ProfileToken name="TikTok Creator" partnerName="tiktok" />
|
|
155
116
|
</div>
|
|
156
117
|
),
|
|
157
118
|
};
|
|
@@ -161,42 +122,39 @@ export const TokenVariations: Story = {
|
|
|
161
122
|
<div style={{ display: "flex", flexWrap: "wrap", gap: "8px" }}>
|
|
162
123
|
<ProfileToken
|
|
163
124
|
name="Verified User"
|
|
164
|
-
secondaryName="@verified"
|
|
165
|
-
img={avatarUrl}
|
|
166
|
-
partnerName="twitter"
|
|
167
|
-
>
|
|
168
|
-
<VerificationBadge />
|
|
169
|
-
</ProfileToken>
|
|
170
|
-
<ProfileToken
|
|
171
|
-
name="Regular User"
|
|
172
|
-
secondaryName="@regular"
|
|
173
|
-
img={avatarUrl}
|
|
174
125
|
partnerName="twitter"
|
|
126
|
+
verificationType="verified"
|
|
175
127
|
/>
|
|
176
|
-
<ProfileToken
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
128
|
+
<ProfileToken name="Regular User" partnerName="twitter" />
|
|
129
|
+
<ProfileToken name="Facebook User" partnerName="facebook" />
|
|
130
|
+
<ProfileToken name="Instagram User" partnerName="instagram" />
|
|
131
|
+
<ProfileToken name="LinkedIn User" partnerName="linkedin" />
|
|
132
|
+
<ProfileToken name="YouTube Creator" partnerName="youtube" />
|
|
133
|
+
</div>
|
|
134
|
+
),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const LongNamesTruncation: Story = {
|
|
138
|
+
render: () => (
|
|
139
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
|
|
140
|
+
<div
|
|
141
|
+
style={{ maxWidth: "250px", border: "1px solid #ccc", padding: "8px" }}
|
|
142
|
+
>
|
|
143
|
+
<ProfileToken
|
|
144
|
+
name="A Very Long User Name That Should Truncate"
|
|
145
|
+
partnerName="twitter"
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
<div
|
|
149
|
+
style={{ maxWidth: "180px", border: "1px solid #ccc", padding: "8px" }}
|
|
150
|
+
>
|
|
151
|
+
<ProfileToken name="Another Long Name Token" partnerName="facebook" />
|
|
152
|
+
</div>
|
|
153
|
+
<div
|
|
154
|
+
style={{ maxWidth: "120px", border: "1px solid #ccc", padding: "8px" }}
|
|
155
|
+
>
|
|
156
|
+
<ProfileToken name="Very Narrow Token" partnerName="instagram" />
|
|
157
|
+
</div>
|
|
200
158
|
</div>
|
|
201
159
|
),
|
|
202
160
|
};
|
package/src/ProfileToken.tsx
CHANGED
|
@@ -1,20 +1,40 @@
|
|
|
1
|
-
import
|
|
1
|
+
import styled from "styled-components";
|
|
2
2
|
import { Token } from "@sproutsocial/seeds-react-token";
|
|
3
3
|
import { InlineProfile } from "./InlineProfile";
|
|
4
4
|
import type { ProfileTokenProps } from "./types";
|
|
5
5
|
|
|
6
|
+
const StyledProfileToken = styled(Token)`
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
display: inline-flex;
|
|
9
|
+
max-width: 100%;
|
|
10
|
+
vertical-align: middle;
|
|
11
|
+
padding: ${(props) => props.theme.space[100]}
|
|
12
|
+
${(props) => props.theme.space[200]};
|
|
13
|
+
margin: ${(props) => props.theme.space[100]} 0;
|
|
14
|
+
`;
|
|
15
|
+
|
|
6
16
|
/**
|
|
7
|
-
* A ProfileToken component that wraps
|
|
17
|
+
* A ProfileToken component that wraps InlineProfile in a Seeds Token component.
|
|
8
18
|
* This token is not closable and provides a compact way to display profile information inline.
|
|
19
|
+
*
|
|
20
|
+
* ProfileToken enforces design standards by only showing the profile name and network logo.
|
|
21
|
+
* It does not support avatars, secondary names (handles), or custom avatar sizes.
|
|
22
|
+
* Use InlineProfile directly if you need more flexibility.
|
|
9
23
|
*/
|
|
10
|
-
export const ProfileToken
|
|
11
|
-
tokenProps,
|
|
24
|
+
export const ProfileToken = ({
|
|
12
25
|
className,
|
|
26
|
+
onClick,
|
|
27
|
+
tokenProps,
|
|
13
28
|
...props
|
|
14
|
-
}) => (
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
}: ProfileTokenProps) => (
|
|
30
|
+
<StyledProfileToken
|
|
31
|
+
className={className}
|
|
32
|
+
closeable={false}
|
|
33
|
+
onClick={onClick}
|
|
34
|
+
{...tokenProps}
|
|
35
|
+
>
|
|
36
|
+
<InlineProfile size="small" {...props} />
|
|
37
|
+
</StyledProfileToken>
|
|
18
38
|
);
|
|
19
39
|
|
|
20
40
|
export default ProfileToken;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Icon } from "@sproutsocial/seeds-react-icon";
|
|
2
|
+
import type { TypeProfileNetwork, TypeProfileVerification } from "./types";
|
|
3
|
+
|
|
4
|
+
// TODO: Move these to a more appropriate location
|
|
5
|
+
const VERIFICATION_COLORS = {
|
|
6
|
+
twitter: "#000000",
|
|
7
|
+
facebook: "#1877F2",
|
|
8
|
+
x: "#000000",
|
|
9
|
+
linkedin: "#0A66C2",
|
|
10
|
+
instagram: "#e4405f",
|
|
11
|
+
gray: "#515e5f",
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Gets the verification color based on partnerName and verification type
|
|
16
|
+
*/
|
|
17
|
+
export const getVerificationColor = (
|
|
18
|
+
partnerName?: TypeProfileNetwork,
|
|
19
|
+
verificationType?: TypeProfileVerification
|
|
20
|
+
): string => {
|
|
21
|
+
if (!verificationType || verificationType === "not_verified") return "";
|
|
22
|
+
|
|
23
|
+
switch (verificationType) {
|
|
24
|
+
case "blue_verified":
|
|
25
|
+
// Use partner-specific blue color
|
|
26
|
+
return partnerName === "facebook"
|
|
27
|
+
? VERIFICATION_COLORS.facebook
|
|
28
|
+
: VERIFICATION_COLORS.twitter;
|
|
29
|
+
case "gray_verified":
|
|
30
|
+
return VERIFICATION_COLORS.gray;
|
|
31
|
+
case "verified":
|
|
32
|
+
default:
|
|
33
|
+
// Default to partner color or Twitter blue
|
|
34
|
+
if (partnerName && partnerName in VERIFICATION_COLORS) {
|
|
35
|
+
return VERIFICATION_COLORS[
|
|
36
|
+
partnerName as keyof typeof VERIFICATION_COLORS
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
return VERIFICATION_COLORS.twitter;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export interface TypeVerifiedProfileIconProps {
|
|
44
|
+
partnerName?: TypeProfileNetwork;
|
|
45
|
+
verificationType: TypeProfileVerification;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const VerifiedProfileIcon = ({
|
|
49
|
+
partnerName,
|
|
50
|
+
verificationType,
|
|
51
|
+
}: TypeVerifiedProfileIconProps) => {
|
|
52
|
+
if (!verificationType || verificationType === "not_verified") return;
|
|
53
|
+
|
|
54
|
+
const verificationColor = getVerificationColor(partnerName, verificationType);
|
|
55
|
+
return (
|
|
56
|
+
<Icon
|
|
57
|
+
title="Verified"
|
|
58
|
+
aria-label="Verified account"
|
|
59
|
+
color={verificationColor}
|
|
60
|
+
name="verified"
|
|
61
|
+
ml="150"
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { render, screen } from "@sproutsocial/seeds-react-testing-library";
|
|
3
2
|
import { InlineProfile } from "../InlineProfile";
|
|
4
3
|
|
|
@@ -9,7 +8,7 @@ describe("InlineProfile", () => {
|
|
|
9
8
|
name="John Doe"
|
|
10
9
|
secondaryName="@johndoe"
|
|
11
10
|
partnerName="twitter"
|
|
12
|
-
|
|
11
|
+
avatarUrl="https://example.com/avatar.jpg"
|
|
13
12
|
/>
|
|
14
13
|
);
|
|
15
14
|
|
|
@@ -17,21 +16,6 @@ describe("InlineProfile", () => {
|
|
|
17
16
|
expect(screen.getByText("@johndoe")).toBeInTheDocument();
|
|
18
17
|
});
|
|
19
18
|
|
|
20
|
-
it("renders with children", () => {
|
|
21
|
-
render(
|
|
22
|
-
<InlineProfile
|
|
23
|
-
name="John Doe"
|
|
24
|
-
secondaryName="@johndoe"
|
|
25
|
-
partnerName="twitter"
|
|
26
|
-
img="https://example.com/avatar.jpg"
|
|
27
|
-
>
|
|
28
|
-
Children
|
|
29
|
-
</InlineProfile>
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
expect(screen.getByText("Children")).toBeInTheDocument();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
19
|
it("renders network logo", () => {
|
|
36
20
|
render(
|
|
37
21
|
<InlineProfile
|
|
@@ -39,7 +23,7 @@ describe("InlineProfile", () => {
|
|
|
39
23
|
secondaryName="@johndoe"
|
|
40
24
|
partnerName="twitter"
|
|
41
25
|
partnerLogoLabel="Twitter Logo"
|
|
42
|
-
|
|
26
|
+
avatarUrl="https://example.com/avatar.jpg"
|
|
43
27
|
/>
|
|
44
28
|
);
|
|
45
29
|
|
|
@@ -52,7 +36,7 @@ describe("InlineProfile", () => {
|
|
|
52
36
|
<InlineProfile
|
|
53
37
|
name="John Doe"
|
|
54
38
|
secondaryName="@johndoe"
|
|
55
|
-
|
|
39
|
+
avatarUrl="https://example.com/avatar.jpg"
|
|
56
40
|
/>
|
|
57
41
|
);
|
|
58
42
|
|
|
@@ -66,7 +50,7 @@ describe("InlineProfile", () => {
|
|
|
66
50
|
<InlineProfile
|
|
67
51
|
name="John Doe"
|
|
68
52
|
partnerName="twitter"
|
|
69
|
-
|
|
53
|
+
avatarUrl="https://example.com/avatar.jpg"
|
|
70
54
|
/>
|
|
71
55
|
);
|
|
72
56
|
|
|
@@ -74,10 +58,26 @@ describe("InlineProfile", () => {
|
|
|
74
58
|
expect(screen.queryByText(/@/)).not.toBeInTheDocument();
|
|
75
59
|
});
|
|
76
60
|
|
|
77
|
-
it("
|
|
61
|
+
it("hides avatar when avatarUrl prop is not provided", () => {
|
|
78
62
|
render(<InlineProfile name="John Doe" />);
|
|
79
63
|
|
|
80
|
-
|
|
81
|
-
expect(
|
|
64
|
+
// Avatar should not be rendered when no URL provided
|
|
65
|
+
expect(screen.queryByText("JD")).not.toBeInTheDocument();
|
|
66
|
+
// But name should still be visible
|
|
67
|
+
expect(screen.getByText("John Doe")).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("renders with verification badge when verified", () => {
|
|
71
|
+
render(
|
|
72
|
+
<InlineProfile
|
|
73
|
+
name="John Doe"
|
|
74
|
+
secondaryName="@johndoe"
|
|
75
|
+
partnerName="twitter"
|
|
76
|
+
avatarUrl="https://example.com/avatar.jpg"
|
|
77
|
+
verificationType="verified"
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(screen.getByLabelText("Verified account")).toBeInTheDocument();
|
|
82
82
|
});
|
|
83
83
|
});
|