@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.
@@ -1,186 +1,4 @@
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
+ export {
2
+ ProfileCardHybrid as ProfileCard,
3
+ ProfileCardHybrid as default,
4
+ } from "./ProfileCardHybrid";
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Hybrid ProfileCard component.
3
+ * Routes between the Tailwind implementation (preferred) and the
4
+ * styled-components implementation (for consumers using styled-system props or
5
+ * a styled() extension).
6
+ */
7
+
8
+ import { needsStyledComponents } from "@sproutsocial/seeds-react-system-props";
9
+ import type { TypeProfileCardProps } from "./types";
10
+ import { ProfileCardStyled } from "./ProfileCardStyled";
11
+ import { ProfileCardTailwind } from "./ProfileCardTailwind";
12
+
13
+ export const ProfileCardHybrid = (props: TypeProfileCardProps) => {
14
+ // Styled-system props may be passed at the top level or nested inside
15
+ // cardProps (which the Styled path spreads onto the real styled(Card)). The
16
+ // Tailwind path can only forward DOM-safe attributes, so route either case to
17
+ // the styled-components implementation to preserve backward compatibility.
18
+ if (
19
+ needsStyledComponents(props) ||
20
+ (props.cardProps && needsStyledComponents(props.cardProps))
21
+ ) {
22
+ return <ProfileCardStyled {...props} />;
23
+ }
24
+ return <ProfileCardTailwind {...props} />;
25
+ };
@@ -0,0 +1,188 @@
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
+ * Styled-components implementation of ProfileCard.
89
+ *
90
+ * Preserved as the legacy rendering path: the Hybrid routes here when the
91
+ * component is extended via styled() or receives styled-system props. The
92
+ * markup is byte-for-byte identical to the pre-migration ProfileCard,
93
+ * including the dynamic banner background logic.
94
+ */
95
+ export const ProfileCardStyled = ({
96
+ partnerName,
97
+ name,
98
+ secondaryName,
99
+ avatarUrl,
100
+ subtext,
101
+ description,
102
+ verificationType,
103
+ profileUrl,
104
+ location,
105
+ bannerColor,
106
+ bannerContent,
107
+ metadata,
108
+ profileActions,
109
+ footer,
110
+ cardProps,
111
+ }: TypeProfileCardProps) => {
112
+ return (
113
+ <StyledProfileCard role="presentation" {...cardProps}>
114
+ <StyledCardHeader $bannerColor={bannerColor} $partnerName={partnerName}>
115
+ <Avatar src={avatarUrl} name={name} size="60px" mt={450} />
116
+ {bannerContent && <BannerContent>{bannerContent}</BannerContent>}
117
+ </StyledCardHeader>
118
+
119
+ <ActionsSection>{profileActions}</ActionsSection>
120
+
121
+ <StyledCardContent>
122
+ <ProfileNameSection>
123
+ {partnerName && (
124
+ <PartnerLogo
125
+ partnerName={partnerName}
126
+ aria-label={`${partnerName} profile`}
127
+ size="small"
128
+ />
129
+ )}
130
+ <Text.SubHeadline as="h2">{name}</Text.SubHeadline>
131
+ </ProfileNameSection>
132
+
133
+ {subtext && (
134
+ <Text fontSize={200} color="text.subtext">
135
+ {subtext}
136
+ </Text>
137
+ )}
138
+
139
+ {secondaryName && (
140
+ <InfoRow>
141
+ {profileUrl ? (
142
+ <Link href={profileUrl} external>
143
+ <Text fontSize={200}>{secondaryName}</Text>
144
+ <Icon
145
+ name="arrow-right-up-outline"
146
+ aria-hidden
147
+ ml={100}
148
+ size="small"
149
+ />
150
+ </Link>
151
+ ) : (
152
+ <Text.SmallByline color="text.subtext">
153
+ {secondaryName}
154
+ </Text.SmallByline>
155
+ )}
156
+ {verificationType && (
157
+ <VerifiedProfileIcon
158
+ partnerName={partnerName}
159
+ verificationType={verificationType}
160
+ />
161
+ )}
162
+ </InfoRow>
163
+ )}
164
+
165
+ {description && <Text fontSize={200}>{description}</Text>}
166
+
167
+ {location && (
168
+ <InfoRow>
169
+ <Icon name="location-pin-outline" aria-hidden size="small" />
170
+ <Text fontSize={200}>{location}</Text>
171
+ </InfoRow>
172
+ )}
173
+
174
+ {metadata && metadata.length > 0 && (
175
+ <InfoRow>
176
+ {metadata.map((info, index) => (
177
+ <Text fontSize={200} key={index}>
178
+ {info}
179
+ </Text>
180
+ ))}
181
+ </InfoRow>
182
+ )}
183
+ </StyledCardContent>
184
+
185
+ {footer && <CardFooter>{footer}</CardFooter>}
186
+ </StyledProfileCard>
187
+ );
188
+ };
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Tailwind/CSS-class implementation of ProfileCard.
3
+ * Uses Seeds ProfileCard CSS classes from profile-card.css.
4
+ *
5
+ * The composed Card/CardHeader/CardContent/CardFooter components are not yet
6
+ * migrated, so their styling is applied via their className prop. The dynamic
7
+ * header background (bannerColor / network color) is applied via an inline
8
+ * style, since no styled-components theme context is available on this path.
9
+ */
10
+
11
+ import { PartnerLogo } from "@sproutsocial/seeds-react-partner-logo";
12
+ import { Avatar } from "@sproutsocial/seeds-react-avatar";
13
+ import { Icon } from "@sproutsocial/seeds-react-icon";
14
+ import { Link } from "@sproutsocial/seeds-react-link";
15
+ import { Text } from "@sproutsocial/seeds-react-text";
16
+ import type { TypeCardProps } from "@sproutsocial/seeds-react-card";
17
+ import type { TypeProfileCardProps } from "./types";
18
+ import { VerifiedProfileIcon } from "./VerifiedProfileIcon";
19
+ import { cn } from "./cn";
20
+
21
+ /**
22
+ * A flexible profile component that can display user profiles in various formats
23
+ * across different social networks with consistent styling and behavior.
24
+ */
25
+ export const ProfileCardTailwind = ({
26
+ partnerName,
27
+ name,
28
+ secondaryName,
29
+ avatarUrl,
30
+ subtext,
31
+ description,
32
+ verificationType,
33
+ profileUrl,
34
+ location,
35
+ bannerColor,
36
+ bannerContent,
37
+ metadata,
38
+ profileActions,
39
+ footer,
40
+ className,
41
+ cardProps,
42
+ }: TypeProfileCardProps) => {
43
+ const {
44
+ className: cardClassName,
45
+ role: cardRole,
46
+ ...restCardProps
47
+ } = cardProps ?? ({} as TypeCardProps);
48
+ return (
49
+ <div
50
+ role={cardRole ?? "presentation"}
51
+ className={cn("seeds-profile-card", className, cardClassName)}
52
+ // The Hybrid routes any cardProps carrying styled-system props to the SC
53
+ // path, so whatever reaches this div is DOM-safe. Narrow the residual
54
+ // TypeCardProps union (which TS cannot directly reconcile with DOM attrs)
55
+ // to say so — it cannot leak Card-only props here.
56
+ {...(restCardProps as unknown as React.HTMLAttributes<HTMLDivElement>)}
57
+ >
58
+ <div
59
+ className="seeds-profile-card-header"
60
+ style={{
61
+ ["--seeds-profile-card-banner-bg" as string]:
62
+ bannerColor ??
63
+ (partnerName ? `var(--color-network-${partnerName})` : undefined),
64
+ }}
65
+ >
66
+ <Avatar src={avatarUrl} name={name} size="60px" mt={450} />
67
+ {bannerContent && (
68
+ <div className="seeds-profile-card-banner-content">
69
+ {bannerContent}
70
+ </div>
71
+ )}
72
+ </div>
73
+
74
+ <div className="seeds-profile-card-actions">{profileActions}</div>
75
+
76
+ <div className="seeds-profile-card-content">
77
+ <div className="seeds-profile-card-name-section">
78
+ {partnerName && (
79
+ <PartnerLogo
80
+ partnerName={partnerName}
81
+ aria-label={`${partnerName} profile`}
82
+ size="small"
83
+ />
84
+ )}
85
+ <Text.SubHeadline as="h2">{name}</Text.SubHeadline>
86
+ </div>
87
+
88
+ {subtext && (
89
+ <Text fontSize={200} color="text.subtext">
90
+ {subtext}
91
+ </Text>
92
+ )}
93
+
94
+ {secondaryName && (
95
+ <div className="seeds-profile-card-info-row">
96
+ {profileUrl ? (
97
+ <Link href={profileUrl} external>
98
+ <Text fontSize={200}>{secondaryName}</Text>
99
+ <Icon
100
+ name="arrow-right-up-outline"
101
+ aria-hidden
102
+ ml={100}
103
+ size="small"
104
+ />
105
+ </Link>
106
+ ) : (
107
+ <Text.SmallByline color="text.subtext">
108
+ {secondaryName}
109
+ </Text.SmallByline>
110
+ )}
111
+ {verificationType && (
112
+ <VerifiedProfileIcon
113
+ partnerName={partnerName}
114
+ verificationType={verificationType}
115
+ />
116
+ )}
117
+ </div>
118
+ )}
119
+
120
+ {description && (
121
+ <Text color="text.subtext" fontSize={200}>
122
+ {description}
123
+ </Text>
124
+ )}
125
+
126
+ {location && (
127
+ <div className="seeds-profile-card-info-row">
128
+ <Icon
129
+ color="icon.base"
130
+ name="location-pin-outline"
131
+ aria-hidden
132
+ size="small"
133
+ />
134
+ <Text color="text.subtext" fontSize={200}>
135
+ {location}
136
+ </Text>
137
+ </div>
138
+ )}
139
+
140
+ {metadata && metadata.length > 0 && (
141
+ <div className="seeds-profile-card-info-row">
142
+ {metadata.map((info, index) => (
143
+ <Text color="text.subtext" fontSize={200} key={index}>
144
+ {info}
145
+ </Text>
146
+ ))}
147
+ </div>
148
+ )}
149
+ </div>
150
+
151
+ {footer && <div className="seeds-profile-card-footer">{footer}</div>}
152
+ </div>
153
+ );
154
+ };
@@ -134,6 +134,22 @@ export const TokenVariations: Story = {
134
134
  ),
135
135
  };
136
136
 
137
+ export const TailwindClassOverride: Story = {
138
+ name: "Tailwind class override",
139
+ render: () => (
140
+ <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
141
+ <ProfileToken name="Default spacing" partnerName="twitter" />
142
+ {/* Utility classes (p-300, mx-200) override the component CSS,
143
+ proving the @layer components fix lets Tailwind win the cascade. */}
144
+ <ProfileToken
145
+ name="Override Demo"
146
+ partnerName="twitter"
147
+ className="p-300 mx-200"
148
+ />
149
+ </div>
150
+ ),
151
+ };
152
+
137
153
  export const LongNamesTruncation: Story = {
138
154
  render: () => (
139
155
  <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
@@ -1,40 +1,4 @@
1
- import styled from "styled-components";
2
- import { Token } from "@sproutsocial/seeds-react-token";
3
- import { InlineProfile } from "./InlineProfile";
4
- import type { ProfileTokenProps } from "./types";
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
-
16
- /**
17
- * A ProfileToken component that wraps InlineProfile in a Seeds Token component.
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.
23
- */
24
- export const ProfileToken = ({
25
- className,
26
- onClick,
27
- tokenProps,
28
- ...props
29
- }: ProfileTokenProps) => (
30
- <StyledProfileToken
31
- className={className}
32
- closeable={false}
33
- onClick={onClick}
34
- {...tokenProps}
35
- >
36
- <InlineProfile size="small" {...props} />
37
- </StyledProfileToken>
38
- );
39
-
40
- export default ProfileToken;
1
+ export {
2
+ ProfileTokenHybrid as ProfileToken,
3
+ ProfileTokenHybrid as default,
4
+ } from "./ProfileTokenHybrid";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Hybrid ProfileToken component.
3
+ * Routes between the Tailwind implementation (preferred) and the
4
+ * styled-components implementation (for consumers using styled-system props or
5
+ * a styled() extension).
6
+ */
7
+
8
+ import { needsStyledComponents } from "@sproutsocial/seeds-react-system-props";
9
+ import type { ProfileTokenProps } from "./types";
10
+ import { ProfileTokenStyled } from "./ProfileTokenStyled";
11
+ import { ProfileTokenTailwind } from "./ProfileTokenTailwind";
12
+
13
+ export const ProfileTokenHybrid = (props: ProfileTokenProps) => {
14
+ if (needsStyledComponents(props)) {
15
+ return <ProfileTokenStyled {...props} />;
16
+ }
17
+ return <ProfileTokenTailwind {...props} />;
18
+ };
@@ -0,0 +1,37 @@
1
+ import styled from "styled-components";
2
+ import { Token } from "@sproutsocial/seeds-react-token";
3
+ import { InlineProfile } from "./InlineProfile";
4
+ import type { ProfileTokenProps } from "./types";
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
+
16
+ /**
17
+ * Styled-components implementation of ProfileToken.
18
+ *
19
+ * Preserved as the legacy rendering path: the Hybrid routes here when the
20
+ * component is extended via styled() or receives styled-system props. The
21
+ * markup is byte-for-byte identical to the pre-migration ProfileToken.
22
+ */
23
+ export const ProfileTokenStyled = ({
24
+ className,
25
+ onClick,
26
+ tokenProps,
27
+ ...props
28
+ }: ProfileTokenProps) => (
29
+ <StyledProfileToken
30
+ className={className}
31
+ closeable={false}
32
+ onClick={onClick}
33
+ {...tokenProps}
34
+ >
35
+ <InlineProfile size="small" {...props} />
36
+ </StyledProfileToken>
37
+ );