Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use radix-ui for avatars #9633

Merged
merged 3 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
"singleQuote": false
}
}
]
],
"plugins": ["prettier-plugin-tailwindcss"]
}
26 changes: 2 additions & 24 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,7 @@
{
"git.ignoreLimitWarning": true,
"eslint.packageManager": "yarn",
"typescript.tsdk": "node_modules/typescript/lib",
"editor.tabSize": 2,
"eslint.workingDirectories": [
{
"directory": "./packages/server",
"changeProcessCWD": true
},
{
"directory": "./packages/client",
"changeProcessCWD": true
}
],
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "typescript",
"autoFix": true
},
{
"language": "typescriptreact",
"autoFix": true
}
],
"npm.packageManager": "yarn",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
Expand Down Expand Up @@ -53,5 +30,6 @@
"**/graphql/private/schema.graphql": true,
"**/graphql/public/schema.graphql": true,
"**/resolverTypes.ts": true
}
},
"tailwindCSS.experimental.classRegex": [["clsx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]]
}
2 changes: 1 addition & 1 deletion packages/client/components/ActionMeetingAgendaItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const ActionMeetingAgendaItems = (props: Props) => {
</MeetingTopBar>
<PhaseWrapper>
<AgendaVerbatim>
<Avatar picture={picture} size={64} />
<Avatar picture={picture} className={'h-16 w-16'} />
<StyledHeading>{content}</StyledHeading>
</AgendaVerbatim>
<StyledCopy>{`${preferredName}, what do you need?`}</StyledCopy>
Expand Down
5 changes: 2 additions & 3 deletions packages/client/components/ActionMeetingUpdatesPrompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import styled from '@emotion/styled'
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import {useFragment} from 'react-relay'
import ActionMeetingUpdatesPromptTeamHelpText from '../modules/meeting/components/ActionMeetingUpdatesPromptTeamHelpText'
import defaultUserAvatar from '../styles/theme/images/avatar-user.svg'
import {ActionMeetingUpdatesPrompt_meeting$key} from '../__generated__/ActionMeetingUpdatesPrompt_meeting.graphql'
import ActionMeetingUpdatesPromptTeamHelpText from '../modules/meeting/components/ActionMeetingUpdatesPromptTeamHelpText'
import Avatar from './Avatar/Avatar'
import PhaseHeaderDescription from './PhaseHeaderDescription'
import PhaseHeaderTitle from './PhaseHeaderTitle'
Expand Down Expand Up @@ -89,7 +88,7 @@ const ActionMeetingUpdatesPrompt = (props: Props) => {
const taskCount = tasks.edges.length
return (
<StyledPrompt>
<Avatar picture={picture || defaultUserAvatar} size={64} />
<Avatar picture={picture} className={'h-16 w-16'} />
<PromptText>
<StyledHeader className='max-w-full'>
{prefix}
Expand Down
94 changes: 18 additions & 76 deletions packages/client/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,92 +1,34 @@
import styled from '@emotion/styled'
import React, {forwardRef, useState} from 'react'
import clsx from 'clsx'
import React, {forwardRef} from 'react'
import defaultUserAvatar from '../../styles/theme/images/avatar-user.svg'
import AvatarBadge from '../AvatarBadge/AvatarBadge'

type ImageBlockProps = Pick<Props, 'sansRadius' | 'sansShadow' | 'picture' | 'size' | 'onClick'>

const ImageBlock = styled('div')<ImageBlockProps>(
({sansRadius, sansShadow, picture, size, onClick}) => ({
backgroundImage: `url(${picture})`,
backgroundPosition: 'center center',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
borderRadius: sansRadius ? 0 : '100%',
boxShadow: sansShadow ? 'none' : undefined,
cursor: onClick ? 'pointer' : 'default',
display: 'block',
flexShrink: 0,
width: size,
height: size
})
)

const BadgeBlock = styled('div')({
alignItems: 'center',
display: 'flex',
height: '25%',
justifyContent: 'center',
position: 'absolute',
right: 0,
top: 0,
width: '25%'
})

const BadgeBlockInner = styled('div')({
flexShrink: 0
})
import {Avatar as AvatarRoot} from '../../ui/Avatar/Avatar'
import {AvatarFallback} from '../../ui/Avatar/AvatarFallback'
import {AvatarImage} from '../../ui/Avatar/AvatarImage'

interface Props {
alt?: string
className?: string
hasBadge?: boolean
isConnected?: boolean
onClick?: (e?: React.MouseEvent) => void
onMouseEnter?: () => void
onTransitionEnd?: () => void
picture: string
sansRadius?: boolean
sansShadow?: boolean
size: number
picture?: string | null
}

const Avatar = forwardRef((props: Props, ref: any) => {
const {
className,
hasBadge,
isConnected,
onClick,
onMouseEnter,
onTransitionEnd,
picture,
sansRadius,
sansShadow,
size
} = props
const [imageUrl, setImageUrl] = useState(picture || defaultUserAvatar)
const onError = () => {
setImageUrl(defaultUserAvatar)
}
const Avatar = forwardRef<HTMLDivElement, Props>((props, ref) => {
const {alt, className, onClick, onTransitionEnd, onMouseEnter, picture} = props
return (
<ImageBlock
onTransitionEnd={onTransitionEnd}
className={className}
ref={ref}
<AvatarRoot
onClick={onClick}
onTransitionEnd={onTransitionEnd}
onMouseEnter={onMouseEnter}
sansRadius={sansRadius}
sansShadow={sansShadow}
picture={imageUrl}
size={size}
ref={ref}
className={clsx(`${onClick && 'cursor-pointer'}`, className)}
>
<img src={imageUrl} className='hidden' onError={onError} />
{hasBadge && (
<BadgeBlock>
<BadgeBlockInner>
<AvatarBadge isConnected={isConnected || false} />
</BadgeBlockInner>
</BadgeBlock>
)}
</ImageBlock>
<AvatarImage src={picture || defaultUserAvatar} alt={alt || 'Avatar'} />
<AvatarFallback>
<img src={defaultUserAvatar} alt={alt || 'Avatar not found'} />
</AvatarFallback>
</AvatarRoot>
)
})

Expand Down
4 changes: 2 additions & 2 deletions packages/client/components/AvatarList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import graphql from 'babel-plugin-relay/macro'
import React, {ReactElement, useLayoutEffect, useRef, useState} from 'react'
import {useFragment} from 'react-relay'
import useResizeObserver from '~/hooks/useResizeObserver'
import {AvatarList_users$key} from '../__generated__/AvatarList_users.graphql'
import useOverflowAvatars from '../hooks/useOverflowAvatars'
import {TransitionStatus} from '../hooks/useTransition'
import {BezierCurve} from '../types/constEnums'
import {AvatarList_users$key} from '../__generated__/AvatarList_users.graphql'
import AvatarListUser from './AvatarListUser'
import OverflowAvatar from './OverflowAvatar'

Expand Down Expand Up @@ -117,7 +117,7 @@ const AvatarList = (props: Props) => {
onTransitionEnd={onTransitionEnd}
status={status}
offset={offsetSize * displayIdx}
width={size}
className={`${size === 28 ? 'h-7 w-7' : size === 46 ? 'h-[46px] w-[46px]' : ''}`}
borderColor={borderColor}
/>
)
Expand Down
40 changes: 10 additions & 30 deletions packages/client/components/AvatarListUser.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import styled from '@emotion/styled'
import graphql from 'babel-plugin-relay/macro'
import clsx from 'clsx'
import React from 'react'
import {useFragment} from 'react-relay'
import {TransitionStatus} from '~/hooks/useTransition'
import {AvatarListUser_user$key} from '../__generated__/AvatarListUser_user.graphql'
import {MenuPosition} from '../hooks/useCoords'
import useTooltip from '../hooks/useTooltip'
import {BezierCurve} from '../types/constEnums'
import {AvatarListUser_user$key} from '../__generated__/AvatarListUser_user.graphql'
import Avatar from './Avatar/Avatar'

const Wrapper = styled('div')<{offset: number; isColumn?: boolean}>(({offset, isColumn}) => ({
Expand All @@ -15,26 +16,6 @@ const Wrapper = styled('div')<{offset: number; isColumn?: boolean}>(({offset, is
transition: `all 300ms ${BezierCurve.DECELERATE}`
}))

const StyledAvatar = styled(Avatar)<{
status?: TransitionStatus
isAnimated: boolean
borderColor?: string
width: number
}>(({status, isAnimated, borderColor = '#fff', width}) => ({
border: `${width >= 40 ? '3px' : '2px'} solid ${borderColor}`,
opacity: !isAnimated
? undefined
: status === TransitionStatus.EXITING || status === TransitionStatus.MOUNTED
? 0
: 1,
transform: !isAnimated
? undefined
: status === TransitionStatus.EXITING || status === TransitionStatus.MOUNTED
? 'scale(0)'
: 'scale(1)',
transition: `all 300ms ${BezierCurve.DECELERATE}`
}))

interface Props {
className?: string
offset: number
Expand All @@ -43,7 +24,6 @@ interface Props {
onTransitionEnd?: () => void
status?: TransitionStatus
user: AvatarListUser_user$key
width: number
onClick?: () => void
borderColor?: string
}
Expand All @@ -57,7 +37,6 @@ const AvatarListUser = (props: Props) => {
status,
offset,
isAnimated,
width,
onClick,
borderColor
} = props
Expand All @@ -74,6 +53,9 @@ const AvatarListUser = (props: Props) => {
const {tooltipPortal, openTooltip, closeTooltip, originRef} = useTooltip<HTMLDivElement>(
MenuPosition.UPPER_CENTER
)
const isAnimating =
isAnimated && (status === TransitionStatus.EXITING || status === TransitionStatus.MOUNTED)

return (
<Wrapper
ref={originRef}
Expand All @@ -83,15 +65,13 @@ const AvatarListUser = (props: Props) => {
onMouseOver={openTooltip}
onMouseLeave={closeTooltip}
>
<StyledAvatar
className={className}
status={status}
<Avatar
className={clsx(
`border-solid border-[${borderColor || '#fff'}] duration-300 ease-out ${isAnimating ? 'scale-0 opacity-0' : 'scale-100 opacity-100'}`,
className
)}
onTransitionEnd={onTransitionEnd}
picture={picture}
size={width}
isAnimated={isAnimated}
borderColor={borderColor}
width={width}
/>
{tooltipPortal(preferredName)}
</Wrapper>
Expand Down
26 changes: 4 additions & 22 deletions packages/client/components/DashboardAvatars/DashboardAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ import styled from '@emotion/styled'
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import {commitLocalUpdate, useFragment} from 'react-relay'
import {DashboardAvatar_teamMember$key} from '../../__generated__/DashboardAvatar_teamMember.graphql'
import useAtmosphere from '../../hooks/useAtmosphere'
import {MenuPosition} from '../../hooks/useCoords'
import useMutationProps from '../../hooks/useMutationProps'
import useTooltip from '../../hooks/useTooltip'
import ToggleTeamDrawerMutation from '../../mutations/ToggleTeamDrawerMutation'
import {PALETTE} from '../../styles/paletteV3'
import defaultUserAvatar from '../../styles/theme/images/avatar-user.svg'
import {ElementWidth} from '../../types/constEnums'
import {DashboardAvatar_teamMember$key} from '../../__generated__/DashboardAvatar_teamMember.graphql'
import Avatar from '../Avatar/Avatar'

interface Props {
Expand All @@ -21,20 +19,6 @@ const AvatarWrapper = styled('div')({
width: ElementWidth.DASHBOARD_AVATAR_OVERLAPPED
})

const StyledAvatar = styled(Avatar)<{isConnected: boolean; picture: string}>(
({isConnected, picture}) => ({
// opacity causes transparency making overlap look bad. use img instead
backgroundImage: `${
isConnected ? '' : 'linear-gradient(rgba(255,255,255,.65), rgba(255,255,255,.65)),'
} url(${picture}), url(${defaultUserAvatar})`,
border: `2px solid ${PALETTE.SLATE_200}`,
':hover': {
backgroundImage: `linear-gradient(rgba(255,255,255,.5), rgba(255,255,255,.5)),
url(${picture}), url(${defaultUserAvatar})`
}
})
)

const DashboardAvatar = (props: Props) => {
const {teamMember: teamMemberRef} = props
const teamMember = useFragment(
Expand Down Expand Up @@ -86,13 +70,11 @@ const DashboardAvatar = (props: Props) => {

return (
<AvatarWrapper onMouseEnter={openTooltip} onMouseLeave={closeTooltip}>
<StyledAvatar
{...teamMember}
isConnected={!!isConnected}
<Avatar
onClick={handleClick}
picture={picture || defaultUserAvatar}
picture={picture}
ref={originRef}
size={ElementWidth.DASHBOARD_AVATAR}
className={`h-7 w-7 border-2 border-solid border-slate-200 after:absolute after:h-full after:w-full after:content-[""] hover:after:bg-white/30 ${!isConnected && 'after:bg-white/60'}`}
/>
{tooltipPortal(preferredName)}
</AvatarWrapper>
Expand Down
16 changes: 3 additions & 13 deletions packages/client/components/DeckActivityAvatars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import styled from '@emotion/styled'
import graphql from 'babel-plugin-relay/macro'
import React, {useMemo} from 'react'
import {useFragment} from 'react-relay'
import {DeckActivityAvatars_stage$key} from '../__generated__/DeckActivityAvatars_stage.graphql'
import useAtmosphere from '../hooks/useAtmosphere'
import useTransition, {TransitionStatus} from '../hooks/useTransition'
import {PokerCards} from '../types/constEnums'
import {DeckActivityAvatars_stage$key} from '../__generated__/DeckActivityAvatars_stage.graphql'
import AvatarListUser from './AvatarListUser'

const DeckActivityPanel = styled('div')({
Expand All @@ -19,16 +19,6 @@ const DeckActivityPanel = styled('div')({
zIndex: 100 // show above dimension column
})

const PeekingAvatar = styled(AvatarListUser)<{status?: TransitionStatus}>(({status}) => ({
opacity: status === TransitionStatus.EXITING ? 0 : 1,
transform:
status === TransitionStatus.MOUNTED
? `translate(64px)`
: status === TransitionStatus.EXITING
? 'scale(0)'
: undefined
}))

interface Props {
stage: DeckActivityAvatars_stage$key
}
Expand Down Expand Up @@ -78,15 +68,15 @@ const DeckActivityAvatars = (props: Props) => {
const visibleScoreIdx = peekingUsers.findIndex((user) => user.id === userId)
const displayIdx = visibleScoreIdx === -1 ? idx : visibleScoreIdx
return (
<PeekingAvatar
<AvatarListUser
key={userId}
status={status}
onTransitionEnd={onTransitionEnd}
user={child}
offset={(PokerCards.AVATAR_WIDTH - 10) * displayIdx}
isColumn
isAnimated
width={PokerCards.AVATAR_WIDTH as number}
className={`h-[46px] w-[46px] border-[3px] opacity-100 ${status === TransitionStatus.EXITING ? 'scale-0 opacity-0' : status === TransitionStatus.MOUNTED ? 'translate-x-64' : ''}`}
/>
)
})}
Expand Down