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

[C-4002] Remove all stems buttons in web #7848

Merged
merged 11 commits into from
Mar 16, 2024
2 changes: 1 addition & 1 deletion packages/common/src/models/ErrorReporting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type AdditionalErrorReportInfo = Record<string, unknown>

export type ReportToSentryArgs = {
/**
* The raw JS `Error` to report to sentry. Note: strings are not allowed;
* The raw JS `Error` to report to sentry. Note: strings are not allowed;
* if you don't have an error object to log just create one via new Error('message')
*/
error: Error
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/utils/linking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const externalLinkAllowList = new Set([
'tiktok.com',
'twitter.com',
'x.com',
'blog.audius.co',
'audius.co',
'discord.gg',
'solscan.io'
Expand Down
289 changes: 148 additions & 141 deletions packages/harmony/src/components/button/FollowButton/FollowButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useCallback, useEffect } from 'react'
import { useState, useCallback, useEffect, forwardRef, Ref } from 'react'

import { useTheme, type CSSObject } from '@emotion/react'
import styled from '@emotion/styled'
Expand All @@ -11,7 +11,7 @@ import { IconUserFollowing, IconUserFollow, IconUserUnfollow } from 'icons'

import type { FollowButtonProps } from './types'

const messages = {
const defaultMessages = {
follow: 'Follow',
following: 'Following',
unfollow: 'Unfollow'
Expand All @@ -33,149 +33,156 @@ const InputRoot = styled.input({
/**
* Special button for following or unfollowing a user.
*/
export const FollowButton = (props: FollowButtonProps) => {
const {
variant = 'default',
isFollowing = false,
onUnfollow,
onFollow,
size = 'default',
...other
} = props
const { type } = other
const [value, setValueState] = useControlled({
componentName: 'FollowButton',
controlledProp: isFollowing,
defaultValue: undefined,
stateName: 'following'
})

// Track hover manually to swap text and icon
const [isHovering, setIsHovering] = useState(false)
const [isPressing, setIsPressing] = useState(false)

const handleMouseEnter = useCallback(() => {
setIsHovering(true)
}, [])

const handleMouseDown = useCallback(() => {
setIsPressing(true)
}, [])

const handleMouseLeave = useCallback(() => {
setIsHovering(false)
}, [])

const handleMouseUp = useCallback(() => {
setIsPressing(false)
}, [])

const handlePressIn = useCallback(() => {
setIsHovering(true)
setIsPressing(true)
}, [])

const handlePressOut = useCallback(() => {
setIsHovering(false)
setIsPressing(false)
}, [])

useEffect(() => {}, [value])

const handleClick = useCallback(() => {
if (value) {
onUnfollow?.()
} else {
onFollow?.()
export const FollowButton = forwardRef(
(props: FollowButtonProps, ref: Ref<HTMLButtonElement>) => {
const {
variant = 'default',
isFollowing = false,
onUnfollow,
onFollow,
size = 'default',
fullWidth = true,
messages: messagesProp,
...other
} = props
const messages = { ...defaultMessages, ...messagesProp }
const { type } = other
const [value, setValueState] = useControlled({
componentName: 'FollowButton',
controlledProp: isFollowing,
defaultValue: undefined,
stateName: 'following'
})

// Track hover manually to swap text and icon
const [isHovering, setIsHovering] = useState(false)
const [isPressing, setIsPressing] = useState(false)

const handleMouseEnter = useCallback(() => {
setIsHovering(true)
}, [])

const handleMouseDown = useCallback(() => {
setIsPressing(true)
}, [])

const handleMouseLeave = useCallback(() => {
setIsHovering(false)
}, [])

const handleMouseUp = useCallback(() => {
setIsPressing(false)
}, [])

const handlePressIn = useCallback(() => {
setIsHovering(true)
setIsPressing(true)
}, [])

const handlePressOut = useCallback(() => {
setIsHovering(false)
setIsPressing(false)
}, [])

useEffect(() => {}, [value])

const handleClick = useCallback(() => {
if (value) {
onUnfollow?.()
} else {
onFollow?.()
}
setValueState(!value)
}, [value, setValueState, onUnfollow, onFollow])

const checkedValue = value
let Icon: IconComponent | null = IconUserFollow
let text = messages.follow
if (checkedValue && !isHovering) {
Icon = IconUserFollowing
text = messages.following
} else if (checkedValue && isHovering && !isPressing) {
Icon = IconUserUnfollow
text = messages.unfollow
}
setValueState(!value)
}, [value, setValueState, onUnfollow, onFollow])

const checkedValue = value
let Icon: IconComponent | null = IconUserFollow
let text = messages.follow
if (checkedValue && !isHovering) {
Icon = IconUserFollowing
text = messages.following
} else if (checkedValue && isHovering && !isPressing) {
Icon = IconUserUnfollow
text = messages.unfollow
}

const { color, cornerRadius } = useTheme()

const textColor =
checkedValue || isHovering || isPressing
? color.static.white
: color.primary.primary

const rootCss: CSSObject = {
cursor: 'pointer',
minWidth: size === 'small' ? 128 : 152,
width: '100%',
userSelect: 'none',
borderRadius: variant === 'pill' ? cornerRadius['2xl'] : cornerRadius.s,
background: isPressing
? color.primary.p500
: checkedValue || isHovering
? color.primary.primary
: color.static.white,
border: `1px solid ${
isPressing ? color.primary.p500 : color.primary.primary
}`
}
const { color, cornerRadius } = useTheme()

const textColor =
checkedValue || isHovering || isPressing
? color.static.white
: color.primary.primary

const rootCss: CSSObject = {
cursor: 'pointer',
minWidth: size === 'small' ? 128 : 152,
width: fullWidth ? '100%' : undefined,
userSelect: 'none',
borderRadius: variant === 'pill' ? cornerRadius['2xl'] : cornerRadius.s,
background: isPressing
? color.primary.p500
: checkedValue || isHovering
? color.primary.primary
: color.static.white,
border: `1px solid ${
isPressing ? color.primary.p500 : color.primary.primary
}`
}

// Handles case where user mouses down, moves cursor, and mouses up
useEffect(() => {
if (isPressing) {
document.addEventListener('mouseup', handleMouseUp)
return () => {
document.removeEventListener('mouseup', handleMouseUp)
// Handles case where user mouses down, moves cursor, and mouses up
useEffect(() => {
if (isPressing) {
document.addEventListener('mouseup', handleMouseUp)
return () => {
document.removeEventListener('mouseup', handleMouseUp)
}
}
return undefined
}, [isPressing, handleMouseUp])

const rootProps = {
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
onTouchStart: handlePressIn,
onTouchEnd: handlePressOut
}
return undefined
}, [isPressing, handleMouseUp])

const rootProps = {
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
onTouchStart: handlePressIn,
onTouchEnd: handlePressOut
}

const buttonProps = type === 'checkbox' ? undefined : other
const inputProps = type === 'checkbox' ? other : undefined

return (
<Flex
as={type === 'checkbox' ? 'label' : 'button'}
h={size === 'small' ? 28 : 32}
direction='row'
alignItems='center'
justifyContent='center'
gap='xs'
pv='s'
css={rootCss}
// @ts-ignore flex not smart enough
onClick={handleClick}
{...buttonProps}
{...rootProps}
>
{/* TODO: use theme icon colors (confirm w/design) */}
<Icon height={18} width={18} css={{ path: { fill: textColor } }} />
<Text
variant='label'
size={size === 'small' ? 's' : 'l'}
strength='default'
css={{ color: textColor }}
const buttonProps = type === 'checkbox' ? undefined : other
const inputProps = type === 'checkbox' ? other : undefined

return (
<Flex
// @ts-ignore flex not smart enough
ref={ref}
as={type === 'checkbox' ? 'label' : 'button'}
h={size === 'small' ? 28 : 32}
direction='row'
alignItems='center'
justifyContent='center'
gap='xs'
pv='s'
css={rootCss}
// @ts-ignore flex not smart enough
onClick={handleClick}
{...buttonProps}
{...rootProps}
>
{text}
</Text>
{type === 'checkbox' ? (
<InputRoot {...inputProps} checked={isFollowing} />
) : null}
</Flex>
)
}
{/* TODO: use theme icon colors (confirm w/design) */}
<Icon height={18} width={18} css={{ path: { fill: textColor } }} />
<Text
variant='label'
size={size === 'small' ? 's' : 'l'}
strength='default'
css={{ color: textColor }}
>
{text}
</Text>
{type === 'checkbox' ? (
<InputRoot {...inputProps} checked={isFollowing} />
) : null}
</Flex>
)
}
)
7 changes: 7 additions & 0 deletions packages/harmony/src/components/button/FollowButton/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,11 @@ export type FollowButtonProps = {
* Callback for when an unfollow is triggered.
*/
onUnfollow?: () => void

fullWidth?: boolean
messages?: {
follow?: string
following?: string
unfollow?: string
}
} & InputProps
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,11 @@ import type { ButtonProps } from '../Button/types'
type SocialMedia = 'tiktok' | 'instagram' | 'twitter'

// Omitting aria-label from original type purely for showing in Storybook
export type SocialButtonProps = Omit<ButtonProps, 'aria-label'> & {
export type SocialButtonProps = ButtonProps & {
/**
* Which social media.
*/
socialType: SocialMedia
/**
* Aria label text. Required since these buttons just have icons
*/
'aria-label': string
}

const socialLogos = {
Expand Down Expand Up @@ -55,8 +51,7 @@ export const SocialButton = (props: SocialButtonProps) => {
border: 'transparent',
...(socialType === 'instagram' && instagramBackgroundCss)
}}
>
<SocialLogo color='staticWhite' size='l' />
</Button>
iconLeft={SocialLogo}
/>
)
}
2 changes: 1 addition & 1 deletion packages/harmony/src/components/button/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export { Button } from './Button/Button'
export * from './Button/types'
export { PlainButton } from './PlainButton/PlainButton'
export * from './PlainButton/types'
export { SocialButton } from './SocialButton/SocialButton'
export { SocialButton, SocialButtonProps } from './SocialButton/SocialButton'
export { FollowButton } from './FollowButton/FollowButton'
export * from './FollowButton/types'
export { FilterButton } from './FilterButton/FilterButton'
Expand Down
7 changes: 0 additions & 7 deletions packages/stems/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,6 @@ import './assets/styles/layers.css'
export * from './styles/colors'
export * from './utils/styles'

export {
Button,
ButtonProps,
Type as ButtonType,
Size as ButtonSize
} from './components/Button'

export {
PillButton,
PillButtonProps,
Expand Down
Loading