@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.
@@ -1,15 +1,9 @@
1
- import React from "react";
2
1
  import type { Meta, StoryObj } from "@storybook/react";
3
2
  import { InlineProfile } from "./InlineProfile";
4
- import { Icon } from "@sproutsocial/seeds-react-icon";
5
3
 
6
4
  const avatarUrl =
7
5
  "https://d672eyudr6aq1.cloudfront.net/avatar/cede1373e17c05542b1cc60f427067f2?s=30&d=404";
8
6
 
9
- const VerificationBadge = () => (
10
- <Icon name="check-solid" style={{ color: "#1da1f2" }} />
11
- );
12
-
13
7
  const meta: Meta<typeof InlineProfile> = {
14
8
  title: "Components/Profile/InlineProfile",
15
9
  component: InlineProfile,
@@ -17,6 +11,23 @@ const meta: Meta<typeof InlineProfile> = {
17
11
  layout: "centered",
18
12
  },
19
13
  tags: ["autodocs"],
14
+ argTypes: {
15
+ size: {
16
+ control: { type: "select" },
17
+ options: ["small", "medium", "large"],
18
+ },
19
+ partnerName: {
20
+ control: { type: "select" },
21
+ options: [
22
+ "twitter",
23
+ "facebook",
24
+ "instagram",
25
+ "linkedin",
26
+ "youtube",
27
+ "tiktok",
28
+ ],
29
+ },
30
+ },
20
31
  };
21
32
 
22
33
  export default meta;
@@ -26,7 +37,8 @@ export const Default: Story = {
26
37
  args: {
27
38
  name: "John Doe",
28
39
  secondaryName: "@johndoe",
29
- img: avatarUrl,
40
+ avatarUrl: avatarUrl,
41
+ partnerName: "twitter",
30
42
  },
31
43
  };
32
44
 
@@ -34,8 +46,8 @@ export const WithVerification: Story = {
34
46
  args: {
35
47
  name: "Elon Musk",
36
48
  secondaryName: "@elonmusk",
37
- img: avatarUrl,
38
- children: <VerificationBadge />,
49
+ avatarUrl: avatarUrl,
50
+ isVerified: true,
39
51
  },
40
52
  };
41
53
 
@@ -43,8 +55,18 @@ export const WithPartnerLogo: Story = {
43
55
  args: {
44
56
  name: "meta",
45
57
  secondaryName: "/meta",
46
- img: avatarUrl,
58
+ avatarUrl: avatarUrl,
47
59
  partnerName: "facebook",
60
+ showNetworkLogo: true,
61
+ },
62
+ };
63
+
64
+ export const Vertical: Story = {
65
+ args: {
66
+ name: "Instagram User",
67
+ secondaryName: "@instagramuser",
68
+ avatarUrl: avatarUrl,
69
+ partnerName: "instagram",
48
70
  },
49
71
  };
50
72
 
@@ -59,7 +81,7 @@ export const Large: Story = {
59
81
  args: {
60
82
  name: "large profile",
61
83
  secondaryName: "@large",
62
- avatarSize: "32px",
84
+ size: "large",
63
85
  },
64
86
  };
65
87
 
@@ -69,27 +91,64 @@ export const DifferentNetworks: Story = {
69
91
  <InlineProfile
70
92
  name="twitter user"
71
93
  secondaryName="@twitteruser"
72
- img={avatarUrl}
94
+ avatarUrl={avatarUrl}
73
95
  partnerName="twitter"
74
96
  />
75
97
  <InlineProfile
76
98
  name="facebook user"
77
99
  secondaryName="/facebookuser"
78
- img={avatarUrl}
100
+ avatarUrl={avatarUrl}
79
101
  partnerName="facebook"
80
102
  />
81
103
  <InlineProfile
82
104
  name="instagram user"
83
105
  secondaryName="@instagramuser"
84
- img={avatarUrl}
106
+ avatarUrl={avatarUrl}
85
107
  partnerName="instagram"
86
108
  />
87
109
  <InlineProfile
88
110
  name="linkedin user"
89
111
  secondaryName="linkedinuser"
90
- img={avatarUrl}
112
+ avatarUrl={avatarUrl}
91
113
  partnerName="linkedin"
92
114
  />
93
115
  </div>
94
116
  ),
95
117
  };
118
+
119
+ export const LongNamesTruncation: Story = {
120
+ render: () => (
121
+ <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
122
+ <div
123
+ style={{ maxWidth: "300px", border: "1px solid #ccc", padding: "8px" }}
124
+ >
125
+ <InlineProfile
126
+ name="A Very Long User Name That Should Truncate With Ellipsis"
127
+ secondaryName="@averylonghandlethatshouldalsotruncate"
128
+ avatarUrl={avatarUrl}
129
+ partnerName="twitter"
130
+ />
131
+ </div>
132
+ <div
133
+ style={{ maxWidth: "200px", border: "1px solid #ccc", padding: "8px" }}
134
+ >
135
+ <InlineProfile
136
+ name="Another Super Long Name"
137
+ secondaryName="@anotherlonghandle"
138
+ avatarUrl={avatarUrl}
139
+ partnerName="facebook"
140
+ />
141
+ </div>
142
+ <div
143
+ style={{ maxWidth: "150px", border: "1px solid #ccc", padding: "8px" }}
144
+ >
145
+ <InlineProfile
146
+ name="Very Narrow Container"
147
+ secondaryName="@narrow"
148
+ avatarUrl={avatarUrl}
149
+ partnerName="instagram"
150
+ />
151
+ </div>
152
+ </div>
153
+ ),
154
+ };
@@ -1,23 +1,83 @@
1
1
  import styled from "styled-components";
2
- import { Text } from "@sproutsocial/seeds-react-text";
3
2
  import { PartnerLogo } from "@sproutsocial/seeds-react-partner-logo";
4
3
  import { Avatar } from "@sproutsocial/seeds-react-avatar";
5
4
  import type { TypeInlineProfileProps } from "./types";
5
+ import { VerifiedProfileIcon } from "./VerifiedProfileIcon";
6
6
 
7
- const InlineProfileContainer = styled.div`
8
- display: flex;
7
+ const InlineProfileContainer = styled.span<{
8
+ $size?: "small" | "medium" | "large";
9
+ }>`
10
+ display: inline-flex;
9
11
  flex-direction: row;
10
12
  align-items: center;
13
+ gap: ${(props) => props.theme.space[200]};
11
14
  max-width: 100%;
15
+ ${(props) => props.theme.typography[200]}
16
+ color: ${(props) => props.theme.colors.text.subtext};
17
+ text-align: inherit;
12
18
  `;
13
19
 
14
- const InlineProfileBody = styled.span`
15
- display: flex;
20
+ const InlineProfileAvatar = styled.span`
21
+ display: inline-flex;
22
+ flex-shrink: 0;
23
+ `;
24
+
25
+ const InlineProfileContent = styled.span`
26
+ display: inline-flex;
27
+ align-items: center;
28
+ gap: ${(props) => props.theme.space[200]};
29
+ min-width: 0;
30
+ flex: 1;
31
+ `;
32
+
33
+ const InlineProfileNetwork = styled.span`
34
+ display: inline-flex;
16
35
  align-items: center;
17
- margin-left: 8px;
36
+ flex-shrink: 0;
18
37
  `;
19
38
 
39
+ const InlineProfileText = styled.span`
40
+ display: inline-flex;
41
+ flex-direction: row;
42
+ align-items: center;
43
+ gap: ${(props) => props.theme.space[200]};
44
+ min-width: 0;
45
+ flex: 1;
46
+ `;
47
+
48
+ const InlineProfileName = styled.span`
49
+ display: inline-block;
50
+ ${(props) => props.theme.typography[200]}
51
+ font-family: ${(props) => props.theme.fontFamily};
52
+ font-weight: ${(props) => props.theme.fontWeights.semibold};
53
+ text-transform: lowercase;
54
+ color: ${(props) => props.theme.colors.text.body};
55
+ max-width: 100%;
56
+ overflow: hidden;
57
+ text-overflow: ellipsis;
58
+ white-space: nowrap;
59
+ `;
60
+
61
+ const InlineProfileHandle = styled.span`
62
+ display: inline-block;
63
+ ${(props) => props.theme.typography[200]}
64
+ font-family: ${(props) => props.theme.fontFamily};
65
+ color: ${(props) => props.theme.colors.text.subtext};
66
+ max-width: 100%;
67
+ overflow: hidden;
68
+ text-overflow: ellipsis;
69
+ white-space: nowrap;
70
+ `;
71
+
72
+ const avatarSizeMap = {
73
+ small: "16px",
74
+ medium: "20px",
75
+ large: "24px",
76
+ } as const;
77
+
20
78
  /**
79
+ * A compact inline profile component that displays avatar, name/handle, and verification.
80
+ * Perfect for use in post headers, comments, or other compact spaces.
21
81
  *
22
82
  * @param props - Profile props
23
83
  * @param props.name - Name of the profile
@@ -25,7 +85,7 @@ const InlineProfileBody = styled.span`
25
85
  * @param props.partnerName - Partner logo name
26
86
  * @param props.partnerLogoLabel - Aria-label for the partner logo
27
87
  * @param props.avatarSize - Size of the avatar
28
- * @param props.img - Image URL for the avatar
88
+ * @param props.avatarUrl - Image URL for the avatar
29
89
  * @param props.children - Additional elements to render within the profile component
30
90
  * @returns JSX.Element representing a user profile
31
91
  *
@@ -36,7 +96,7 @@ const InlineProfileBody = styled.span`
36
96
  * partnerName="facebook"
37
97
  * partnerLogoLabel="Facebook Logo"
38
98
  * avatarSize="24px"
39
- * img="https://example.com/avatar.jpg"
99
+ * avatarUrl="https://example.com/avatar.jpg"
40
100
  * >
41
101
  * <Badge text="Moderator" badgeColor="green" />
42
102
  * </InlineProfile>
@@ -46,30 +106,47 @@ export const InlineProfile = ({
46
106
  secondaryName,
47
107
  partnerName,
48
108
  partnerLogoLabel,
49
- avatarSize = "22px",
50
- img,
51
- children,
109
+ size = "medium",
110
+ avatarSize: avatarSizeOverride,
111
+ avatarUrl,
112
+ verificationType,
113
+ className,
52
114
  }: TypeInlineProfileProps) => {
115
+ const displayName = name || secondaryName || "Unknown Profile";
116
+ const avatarSize = avatarSizeOverride || avatarSizeMap[size];
117
+
53
118
  return (
54
- <InlineProfileContainer>
55
- <Avatar size={avatarSize} src={img} name={name} />
56
- {partnerName && (
57
- <PartnerLogo
58
- size="small"
59
- partnerName={partnerName}
60
- ml={300}
61
- aria-label={partnerLogoLabel}
62
- />
63
- )}
64
- <Text fontSize={200} fontWeight="semibold" ml={300} truncated={true}>
65
- {name}
66
- </Text>
67
- {secondaryName && (
68
- <Text fontSize={200} ml={300} fontWeight="normal" truncated={true}>
69
- {secondaryName}
70
- </Text>
71
- )}
72
- <InlineProfileBody>{children}</InlineProfileBody>
119
+ <InlineProfileContainer className={className} $size={size}>
120
+ <InlineProfileAvatar>
121
+ <Avatar src={avatarUrl} name={displayName} size={avatarSize} />
122
+ </InlineProfileAvatar>
123
+
124
+ <InlineProfileContent>
125
+ {partnerName && (
126
+ <InlineProfileNetwork>
127
+ <PartnerLogo
128
+ partnerName={partnerName}
129
+ aria-label={partnerLogoLabel || `${partnerName} profile`}
130
+ size="small"
131
+ />
132
+ </InlineProfileNetwork>
133
+ )}
134
+
135
+ <InlineProfileText>
136
+ {name && <InlineProfileName>{name}</InlineProfileName>}
137
+
138
+ {secondaryName && (
139
+ <InlineProfileHandle>{secondaryName}</InlineProfileHandle>
140
+ )}
141
+ </InlineProfileText>
142
+
143
+ {verificationType && (
144
+ <VerifiedProfileIcon
145
+ partnerName={partnerName}
146
+ verificationType={verificationType}
147
+ />
148
+ )}
149
+ </InlineProfileContent>
73
150
  </InlineProfileContainer>
74
151
  );
75
152
  };
@@ -0,0 +1,264 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Box } from "@sproutsocial/seeds-react-box";
3
+ import { Button } from "@sproutsocial/seeds-react-button";
4
+ import { Icon } from "@sproutsocial/seeds-react-icon";
5
+ import { Text } from "@sproutsocial/seeds-react-text";
6
+ import { ProfileCard } from "./ProfileCard";
7
+
8
+ const meta: Meta<typeof ProfileCard> = {
9
+ title: "Components/Profile/ProfileCard",
10
+ component: ProfileCard,
11
+ parameters: {
12
+ layout: "centered",
13
+ },
14
+ tags: ["autodocs"],
15
+ argTypes: {
16
+ partnerName: {
17
+ control: { type: "select" },
18
+ options: [
19
+ "twitter",
20
+ "facebook",
21
+ "instagram",
22
+ "linkedin",
23
+ "youtube",
24
+ "tiktok",
25
+ "threads",
26
+ "bluesky",
27
+ ],
28
+ },
29
+ },
30
+ };
31
+
32
+ export default meta;
33
+ type Story = StoryObj<typeof meta>;
34
+
35
+ export const Default: Story = {
36
+ args: {
37
+ name: "John Doe",
38
+ secondaryName: "@johndoe",
39
+ partnerName: "twitter",
40
+ avatarUrl: "https://via.placeholder.com/60",
41
+ description:
42
+ "Software developer and tech enthusiast. Building the future one line of code at a time.",
43
+ },
44
+ };
45
+
46
+ export const WithVerification: Story = {
47
+ args: {
48
+ name: "Elon Musk",
49
+ secondaryName: "@elonmusk",
50
+ partnerName: "twitter",
51
+ verificationType: "verified",
52
+ avatarUrl: "https://via.placeholder.com/60",
53
+ subtext: "Technoking of Tesla",
54
+ description:
55
+ "CEO of Tesla and SpaceX. Working to make life multiplanetary.",
56
+ },
57
+ };
58
+
59
+ export const WithProfileDetails: Story = {
60
+ args: {
61
+ name: "Tech Company",
62
+ secondaryName: "@techcompany",
63
+ partnerName: "twitter",
64
+ avatarUrl: "https://via.placeholder.com/60",
65
+ description: "Building the future of technology.",
66
+ profileUrl: "https://twitter.com/techcompany",
67
+ location: "San Francisco, CA",
68
+ metadata: ["1.2K followers", "500 following"],
69
+ },
70
+ };
71
+
72
+ export const InstagramUser: Story = {
73
+ args: {
74
+ name: "Instagram User",
75
+ secondaryName: "@instagramuser",
76
+ partnerName: "instagram",
77
+ avatarUrl: "https://via.placeholder.com/60",
78
+ description:
79
+ "Photographer and content creator sharing moments from around the world.",
80
+ profileUrl: "https://instagram.com/instagramuser",
81
+ location: "New York, NY",
82
+ metadata: ["10.5K followers", "892 following", "1,234 posts"],
83
+ },
84
+ };
85
+
86
+ export const LinkedInUser: Story = {
87
+ args: {
88
+ name: "LinkedIn Professional",
89
+ secondaryName: "linkedinuser",
90
+ partnerName: "linkedin",
91
+ avatarUrl: "https://via.placeholder.com/60",
92
+ subtext: "Senior Product Manager",
93
+ description: "Building products at a leading tech company.",
94
+ },
95
+ };
96
+
97
+ export const WithSubtext: Story = {
98
+ args: {
99
+ name: "Jane Smith",
100
+ secondaryName: "@janesmith",
101
+ partnerName: "twitter",
102
+ avatarUrl: "https://via.placeholder.com/60",
103
+ subtext: "Design Lead @ Sprout Social",
104
+ description:
105
+ "Passionate about creating beautiful and accessible user experiences.",
106
+ },
107
+ };
108
+
109
+ export const WithBannerContent: Story = {
110
+ args: {
111
+ name: "Profile with Banner",
112
+ secondaryName: "@withbanner",
113
+ partnerName: "twitter",
114
+ avatarUrl: "https://via.placeholder.com/60",
115
+ description: "This profile demonstrates the bannerContent slot.",
116
+ bannerContent: (
117
+ <Box
118
+ bg="status.error.background"
119
+ p={300}
120
+ display="flex"
121
+ alignItems="center"
122
+ gap={200}
123
+ >
124
+ <Icon
125
+ name="circle-exclamation"
126
+ color="status.error.text"
127
+ size="small"
128
+ />
129
+ <Text color="status.error.text" fontSize={200}>
130
+ Profile disconnected
131
+ </Text>
132
+ </Box>
133
+ ),
134
+ },
135
+ };
136
+
137
+ export const WithProfileActions: Story = {
138
+ args: {
139
+ name: "Profile with Actions",
140
+ secondaryName: "@withactions",
141
+ partnerName: "twitter",
142
+ avatarUrl: "https://via.placeholder.com/60",
143
+ description: "This profile demonstrates action buttons.",
144
+ profileActions: (
145
+ <>
146
+ <Button appearance="unstyled" size="small" aria-label="Share">
147
+ <Icon name="arrow-up-from-bracket" />
148
+ </Button>
149
+ <Button appearance="unstyled" size="small" aria-label="More options">
150
+ <Icon name="ellipsis-horizontal" />
151
+ </Button>
152
+ </>
153
+ ),
154
+ },
155
+ };
156
+
157
+ export const WithFooter: Story = {
158
+ args: {
159
+ name: "Footer Profile",
160
+ secondaryName: "@footer",
161
+ partnerName: "twitter",
162
+ avatarUrl: "https://via.placeholder.com/60",
163
+ description: "This profile has custom footer content.",
164
+ footer: (
165
+ <div style={{ textAlign: "center", color: "#666", fontSize: "12px" }}>
166
+ Last updated: 2 hours ago
167
+ </div>
168
+ ),
169
+ },
170
+ };
171
+
172
+ export const YouTubeUser: Story = {
173
+ args: {
174
+ name: "YouTube Creator",
175
+ secondaryName: "@youtubecreator",
176
+ partnerName: "youtube",
177
+ avatarUrl: "https://via.placeholder.com/60",
178
+ description: "Content creator sharing videos with the world.",
179
+ },
180
+ };
181
+
182
+ export const TikTokUser: Story = {
183
+ args: {
184
+ name: "TikTok Creator",
185
+ secondaryName: "@tiktokcreator",
186
+ partnerName: "tiktok",
187
+ avatarUrl: "https://via.placeholder.com/60",
188
+ description: "Short-form video content creator.",
189
+ },
190
+ };
191
+
192
+ export const DifferentNetworks: Story = {
193
+ render: () => (
194
+ <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
195
+ <ProfileCard
196
+ name="Twitter User"
197
+ secondaryName="@twitteruser"
198
+ partnerName="twitter"
199
+ avatarUrl="https://via.placeholder.com/60"
200
+ description="Active on Twitter"
201
+ />
202
+ <ProfileCard
203
+ name="Facebook User"
204
+ secondaryName="facebookuser"
205
+ partnerName="facebook"
206
+ avatarUrl="https://via.placeholder.com/60"
207
+ description="Facebook enthusiast"
208
+ />
209
+ <ProfileCard
210
+ name="Instagram User"
211
+ secondaryName="@instagramuser"
212
+ partnerName="instagram"
213
+ avatarUrl="https://via.placeholder.com/60"
214
+ description="Visual storyteller"
215
+ />
216
+ <ProfileCard
217
+ name="LinkedIn User"
218
+ secondaryName="linkedinuser"
219
+ partnerName="linkedin"
220
+ avatarUrl="https://via.placeholder.com/60"
221
+ description="Professional networker"
222
+ />
223
+ <ProfileCard
224
+ name="YouTube Creator"
225
+ secondaryName="@youtubecreator"
226
+ partnerName="youtube"
227
+ avatarUrl="https://via.placeholder.com/60"
228
+ description="Video content creator"
229
+ />
230
+ <ProfileCard
231
+ name="TikTok Creator"
232
+ secondaryName="@tiktokcreator"
233
+ partnerName="tiktok"
234
+ avatarUrl="https://via.placeholder.com/60"
235
+ description="Short-form video creator"
236
+ />
237
+ </div>
238
+ ),
239
+ };
240
+
241
+ export const LongContentTruncation: Story = {
242
+ render: () => (
243
+ <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
244
+ <div style={{ maxWidth: "300px" }}>
245
+ <ProfileCard
246
+ name="A Very Long Profile Name That Should Truncate Properly"
247
+ secondaryName="@averylongtwitterhandlethatshouldalsotruncate"
248
+ partnerName="twitter"
249
+ avatarUrl="https://via.placeholder.com/60"
250
+ description="This is a very long bio description that goes on and on with lots of information about the user and their interests and what they do and all sorts of other details that might need to be displayed in a card format but should truncate properly when it gets too long"
251
+ />
252
+ </div>
253
+ <div style={{ maxWidth: "250px" }}>
254
+ <ProfileCard
255
+ name="Another Long Name"
256
+ secondaryName="@anotherlonghandle"
257
+ partnerName="facebook"
258
+ avatarUrl="https://via.placeholder.com/60"
259
+ description="Another long description that should also truncate when the container is smaller and needs to handle overflow text gracefully"
260
+ />
261
+ </div>
262
+ </div>
263
+ ),
264
+ };