-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[GAL-5439] add default state for web search results (#2391)
* featured collection query working! * much better styling for featured * minor * better * refactored into sections * some cleanup * see all button working * fixed layout styling * feels better at least * user routing working!! * minor styling before main refactorzz * cleaner * still working * lint * lintt * working * better * clean * close * search * oh my * oh my myy * too late * feeedback addressed
- Loading branch information
Showing
13 changed files
with
661 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
import { useMemo } from 'react'; | ||
import { graphql, useFragment } from 'react-relay'; | ||
import styled from 'styled-components'; | ||
|
||
import { SuggestedProfileCardFollowFragment$key } from '~/generated/SuggestedProfileCardFollowFragment.graphql'; | ||
import { SuggestedProfileCardFragment$key } from '~/generated/SuggestedProfileCardFragment.graphql'; | ||
import { contexts } from '~/shared/analytics/constants'; | ||
import { removeNullValues } from '~/shared/relay/removeNullValues'; | ||
import { useLoggedInUserId } from '~/shared/relay/useLoggedInUserId'; | ||
import colors from '~/shared/theme/colors'; | ||
import { getUnescapedBioFirstLine } from '~/utils/sanity'; | ||
|
||
import Badge from '../Badge/Badge'; | ||
import breakpoints from '../core/breakpoints'; | ||
import Markdown from '../core/Markdown/Markdown'; | ||
import { HStack, VStack } from '../core/Spacer/Stack'; | ||
import { BaseM } from '../core/Text/Text'; | ||
import FollowButton from '../Follow/FollowButton'; | ||
import { ProfilePicture } from '../ProfilePicture/ProfilePicture'; | ||
|
||
type Props = { | ||
userRef: SuggestedProfileCardFragment$key; | ||
queryRef: SuggestedProfileCardFollowFragment$key; | ||
showFollowButton?: boolean; | ||
onClick?: () => void; | ||
}; | ||
|
||
export default function SuggestedProfileCard({ | ||
userRef, | ||
queryRef, | ||
onClick, | ||
showFollowButton = true, | ||
}: Props) { | ||
const user = useFragment( | ||
graphql` | ||
fragment SuggestedProfileCardFragment on GalleryUser { | ||
id | ||
username | ||
badges { | ||
name | ||
imageURL | ||
...BadgeFragment | ||
} | ||
bio | ||
galleries { | ||
tokenPreviews { | ||
large | ||
} | ||
hidden | ||
} | ||
...ProfilePictureFragment | ||
...FollowButtonUserFragment | ||
} | ||
`, | ||
userRef | ||
); | ||
|
||
const query = useFragment( | ||
graphql` | ||
fragment SuggestedProfileCardFollowFragment on Query { | ||
...FollowButtonQueryFragment | ||
...useLoggedInUserIdFragment | ||
} | ||
`, | ||
queryRef | ||
); | ||
|
||
const loggedInUserId = useLoggedInUserId(query); | ||
const isOwnProfile = loggedInUserId && loggedInUserId === user?.id; | ||
|
||
if (!user) { | ||
throw new Error('No user available to showcase SuggestedProfileCard'); | ||
} | ||
|
||
const bioFirstLine = useMemo(() => getUnescapedBioFirstLine(user?.bio), [user?.bio]); | ||
|
||
const userGalleries = useMemo(() => { | ||
return user.galleries ?? []; | ||
}, [user.galleries]); | ||
|
||
const tokenPreviews = useMemo(() => { | ||
const gallery = userGalleries.find( | ||
(gallery) => !gallery?.hidden && removeNullValues(gallery?.tokenPreviews).length > 0 | ||
); | ||
|
||
return gallery?.tokenPreviews?.slice(0, 2) ?? []; | ||
}, [userGalleries]); | ||
|
||
const userBadges = useMemo(() => { | ||
const badges = []; | ||
|
||
for (const badge of user.badges ?? []) { | ||
if (badge?.imageURL) { | ||
badges.push(badge); | ||
} | ||
} | ||
|
||
return badges; | ||
}, [user.badges]); | ||
|
||
const shouldShowFollowButton = useMemo( | ||
() => showFollowButton && !isOwnProfile, | ||
[showFollowButton, isOwnProfile] | ||
); | ||
|
||
return ( | ||
<StyledSuggestedProfileCard onClick={onClick}> | ||
<StyledContent gap={4} justify="space-between"> | ||
<TokenPreviewContainer> | ||
{tokenPreviews.map( | ||
(url) => url?.large && <TokenPreview src={url.large} key={url.large} /> | ||
)} | ||
</TokenPreviewContainer> | ||
<ProfileDetailsContainer> | ||
<ProfileDetailsText gap={4}> | ||
<HStack gap={8} align="center" justify="space-between"> | ||
<HStack gap={4} align="center"> | ||
<ProfilePicture userRef={user} size="xs" /> | ||
<Username> | ||
<strong>{user.username}</strong> | ||
</Username> | ||
<HStack align="center" gap={0}> | ||
{userBadges.map((badge) => ( | ||
<Badge key={badge.name} badgeRef={badge} eventContext={contexts['Search']} /> | ||
))} | ||
</HStack> | ||
</HStack> | ||
</HStack> | ||
<VStack gap={8}> | ||
<StyledUserBio> | ||
<Markdown text={bioFirstLine} eventContext={contexts.Search} /> | ||
</StyledUserBio> | ||
{shouldShowFollowButton && ( | ||
<WideFollowButton | ||
userRef={user} | ||
queryRef={query} | ||
source="Search Default profile card" | ||
/> | ||
)} | ||
</VStack> | ||
</ProfileDetailsText> | ||
</ProfileDetailsContainer> | ||
</StyledContent> | ||
</StyledSuggestedProfileCard> | ||
); | ||
} | ||
|
||
const StyledSuggestedProfileCard = styled(HStack)` | ||
border-radius: 4px; | ||
background-color: ${colors.offWhite}; | ||
padding: 8px; | ||
cursor: pointer; | ||
text-decoration: none; | ||
overflow: hidden; | ||
&:hover { | ||
background-color: ${colors.faint}; | ||
} | ||
@media only screen and ${breakpoints.desktop} { | ||
width: 230px; | ||
} | ||
`; | ||
|
||
const StyledContent = styled(VStack)` | ||
height: 100%; | ||
width: 100%; | ||
`; | ||
|
||
const ProfileDetailsContainer = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 8px; | ||
width: 100%; | ||
@media only screen and ${breakpoints.desktop} { | ||
flex-direction: row; | ||
justify-content: space-between; | ||
align-items: center; | ||
} | ||
`; | ||
|
||
const ProfileDetailsText = styled(VStack)` | ||
overflow: hidden; | ||
width: 100%; | ||
`; | ||
|
||
const StyledUserBio = styled(BaseM)` | ||
height: 20px; // ensure consistent height even if bio is not present | ||
font-size: 14px; | ||
font-weight: 400; | ||
line-clamp: 1; | ||
overflow: hidden; | ||
-webkit-line-clamp: 1; | ||
display: -webkit-box; | ||
-webkit-box-orient: vertical; | ||
`; | ||
|
||
export const TokenPreviewContainer = styled.div` | ||
display: grid; | ||
grid-template-columns: repeat(2, 1fr); | ||
min-height: 97px; | ||
grid-gap: 2px; | ||
`; | ||
|
||
export const TokenPreview = styled.img` | ||
height: auto; | ||
width: 100%; | ||
aspect-ratio: 1 / 1; | ||
object-fit: cover; | ||
`; | ||
|
||
const Username = styled(BaseM)` | ||
font-size: 16px; | ||
font-weight: 700; | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
`; | ||
|
||
const WideFollowButton = styled(FollowButton)` | ||
padding: 2px 8px; | ||
width: 100%; | ||
height: 24px; | ||
line-height: 0; | ||
@media only screen and ${breakpoints.desktop} { | ||
width: 100%; | ||
height: 24px; | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { graphql, useLazyLoadQuery } from 'react-relay'; | ||
import styled from 'styled-components'; | ||
|
||
import breakpoints from '~/components/core/breakpoints'; | ||
import { SearchDefaultQuery } from '~/generated/SearchDefaultQuery.graphql'; | ||
|
||
import { VStack } from '../core/Spacer/Stack'; | ||
import { SearchFilterType } from './Search'; | ||
import SearchDefaultSuggestedUsersSection from './SearchDefaultSuggestedUsersSection'; | ||
import SearchDefaultTrendingCuratorsSection from './SearchDefaultTrendingCuratorsSection'; | ||
import SearchDefaultTrendingUsersSection from './SearchDefaultTrendingUsersSection'; | ||
import { SearchItemType } from './types'; | ||
|
||
type Props = { | ||
selectedFilter: SearchFilterType; | ||
onChangeFilter: (filter: SearchFilterType) => void; | ||
onSelect: (item: SearchItemType) => void; | ||
variant?: 'default' | 'compact'; | ||
}; | ||
|
||
export default function SearchDefault({ | ||
variant = 'default', | ||
onSelect, | ||
selectedFilter, | ||
onChangeFilter, | ||
}: Props) { | ||
const query = useLazyLoadQuery<SearchDefaultQuery>( | ||
graphql` | ||
query SearchDefaultQuery { | ||
...SearchDefaultSuggestedUsersSectionFragment | ||
...SearchDefaultTrendingUsersSectionFragment | ||
...SearchDefaultTrendingCuratorsSectionFragment | ||
} | ||
`, | ||
{} | ||
); | ||
|
||
return ( | ||
<SectionWrapper gap={20}> | ||
{!selectedFilter && ( | ||
<VStack gap={16}> | ||
<SearchDefaultSuggestedUsersSection | ||
queryRef={query} | ||
variant={variant} | ||
onSelect={onSelect} | ||
/> | ||
<SearchDefaultTrendingUsersSection | ||
queryRef={query} | ||
variant={variant} | ||
onSelect={onSelect} | ||
/> | ||
</VStack> | ||
)} | ||
<SearchDefaultTrendingCuratorsSection | ||
queryRef={query} | ||
variant={variant} | ||
onSelect={onSelect} | ||
selectedFilter={selectedFilter} | ||
onChangeFilter={onChangeFilter} | ||
showAllButton={true} | ||
/> | ||
</SectionWrapper> | ||
); | ||
} | ||
|
||
const SectionWrapper = styled(VStack)` | ||
padding: 4px; | ||
@media only screen and ${breakpoints.desktop} { | ||
padding: 12px; | ||
} | ||
`; |
Oops, something went wrong.