@sproutsocial/seeds-react-profile 0.3.36 → 0.3.39
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 +33 -0
- package/dist/esm/index.js +256 -24
- 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 +256 -24
- 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 -184
- 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
package/src/ProfileCard.tsx
CHANGED
|
@@ -1,184 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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>{secondaryName}</Text.SmallByline>
|
|
149
|
-
)}
|
|
150
|
-
{verificationType && (
|
|
151
|
-
<VerifiedProfileIcon
|
|
152
|
-
partnerName={partnerName}
|
|
153
|
-
verificationType={verificationType}
|
|
154
|
-
/>
|
|
155
|
-
)}
|
|
156
|
-
</InfoRow>
|
|
157
|
-
)}
|
|
158
|
-
|
|
159
|
-
{description && <Text fontSize={200}>{description}</Text>}
|
|
160
|
-
|
|
161
|
-
{location && (
|
|
162
|
-
<InfoRow>
|
|
163
|
-
<Icon name="location-pin-outline" aria-hidden size="small" />
|
|
164
|
-
<Text fontSize={200}>{location}</Text>
|
|
165
|
-
</InfoRow>
|
|
166
|
-
)}
|
|
167
|
-
|
|
168
|
-
{metadata && metadata.length > 0 && (
|
|
169
|
-
<InfoRow>
|
|
170
|
-
{metadata.map((info, index) => (
|
|
171
|
-
<Text fontSize={200} key={index}>
|
|
172
|
-
{info}
|
|
173
|
-
</Text>
|
|
174
|
-
))}
|
|
175
|
-
</InfoRow>
|
|
176
|
-
)}
|
|
177
|
-
</StyledCardContent>
|
|
178
|
-
|
|
179
|
-
{footer && <CardFooter>{footer}</CardFooter>}
|
|
180
|
-
</StyledProfileCard>
|
|
181
|
-
);
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
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" }}>
|
package/src/ProfileToken.tsx
CHANGED
|
@@ -1,40 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
+
);
|