diff --git a/packages/web/src/components/collection/desktop/CollectionHeader.module.css b/packages/web/src/components/collection/desktop/CollectionHeader.module.css index 40a8904928..3f2e5c5e1e 100644 --- a/packages/web/src/components/collection/desktop/CollectionHeader.module.css +++ b/packages/web/src/components/collection/desktop/CollectionHeader.module.css @@ -157,19 +157,8 @@ } .description { - user-select: none; - min-height: 22px; - white-space: pre-line; - color: var(--neutral); - font-size: var(--font-xs); - font-weight: var(--font-medium); - line-height: 18px; - margin-top: 14px; - margin-bottom: 6px; -} - -.description a { - color: var(--primary); + margin-top: var(--unit-3); + margin-bottom: var(--unit-2); } /* Filter Input */ diff --git a/packages/web/src/components/collection/desktop/CollectionHeader.tsx b/packages/web/src/components/collection/desktop/CollectionHeader.tsx index 93aa6bb937..505316cc1b 100644 --- a/packages/web/src/components/collection/desktop/CollectionHeader.tsx +++ b/packages/web/src/components/collection/desktop/CollectionHeader.tsx @@ -1,15 +1,8 @@ -import { ChangeEvent, useCallback, useState, MouseEvent } from 'react' +import { ChangeEvent, useCallback, useState } from 'react' -import { - squashNewLines, - formatSecondsAsText, - formatDate, - getPathFromAudiusUrl, - isAudiusUrl -} from '@audius/common' +import { formatSecondsAsText, formatDate } from '@audius/common' import { IconHidden, IconPencil } from '@audius/stems' import cn from 'classnames' -import Linkify from 'linkify-react' import { useDispatch } from 'react-redux' import { ReactComponent as IconFilter } from 'assets/img/iconFilter.svg' @@ -18,6 +11,7 @@ import { UserLink } from 'components/link' import RepostFavoritesStats from 'components/repost-favorites-stats/RepostFavoritesStats' import Skeleton from 'components/skeleton/Skeleton' import InfoLabel from 'components/track/InfoLabel' +import { UserGeneratedText } from 'components/user-generated-text' import { open as openEditCollectionModal } from 'store/application/ui/editPlaylistModal/slice' import { Artwork } from './Artwork' @@ -47,8 +41,6 @@ export const CollectionHeader = (props: CollectionHeaderProps) => { tracksLoading, loading, playing, - onClickDescriptionExternalLink, - onClickDescriptionInternalLink, onPlay, variant, gradient, @@ -172,30 +164,13 @@ export const CollectionHeader = (props: CollectionHeaderProps) => { labelValue={numTracks} /> -
- ) => { - const url = event.currentTarget.href - - if (isAudiusUrl(url)) { - const path = getPathFromAudiusUrl(url) - event.nativeEvent.preventDefault() - onClickDescriptionInternalLink(path ?? '/') - } else { - onClickDescriptionExternalLink(event) - } - } - }, - target: (href: string) => { - return isAudiusUrl(href) ? '' : '_blank' - } - }} - > - {squashNewLines(description)} - -
+ + {description} +
{renderStatsRow(isLoading)}
diff --git a/packages/web/src/components/collection/mobile/CollectionHeader.js b/packages/web/src/components/collection/mobile/CollectionHeader.js index 2bca13d585..e7aa1c43b6 100644 --- a/packages/web/src/components/collection/mobile/CollectionHeader.js +++ b/packages/web/src/components/collection/mobile/CollectionHeader.js @@ -1,24 +1,21 @@ -import { memo, useCallback } from 'react' +import { memo } from 'react' import { - Name, Variant, SquareSizes, formatCount, - squashNewLines, formatSecondsAsText, formatDate, OverflowAction } from '@audius/common' import { Button, ButtonType, IconPause, IconPlay } from '@audius/stems' import cn from 'classnames' -import Linkify from 'linkify-react' import PropTypes from 'prop-types' -import { make, useRecord } from 'common/store/analytics/actions' import DynamicImage from 'components/dynamic-image/DynamicImage' import { UserLink } from 'components/link' import Skeleton from 'components/skeleton/Skeleton' +import { UserGeneratedText } from 'components/user-generated-text' import { useCollectionCoverArt } from 'hooks/useCollectionCoverArt' import ActionButtonRow from 'pages/track-page/components/mobile/ActionButtonRow' import StatsButtonRow from 'pages/track-page/components/mobile/StatsButtonRow' @@ -139,19 +136,6 @@ const CollectionHeader = ({ SquareSizes.SIZE_1000_BY_1000 ) - const record = useRecord() - const onDescriptionExternalLink = useCallback( - (event) => { - record( - make(Name.LINK_CLICKING, { - url: event.target.href, - source: 'collection page' - }) - ) - }, - [record] - ) - const collectionLabels = [ { label: 'Tracks', @@ -269,13 +253,12 @@ const CollectionHeader = ({ {renderCollectionLabels()} {description ? ( - -
- {squashNewLines(description)} -
-
+ {description} + ) : null} )} diff --git a/packages/web/src/components/collection/mobile/CollectionHeader.module.css b/packages/web/src/components/collection/mobile/CollectionHeader.module.css index d22a723106..cdf002c949 100644 --- a/packages/web/src/components/collection/mobile/CollectionHeader.module.css +++ b/packages/web/src/components/collection/mobile/CollectionHeader.module.css @@ -155,21 +155,9 @@ } .description { - color: var(--neutral); - font-family: var(--font-family); - font-size: var(--font-m); - font-weight: var(--font-medium); - white-space: pre-line; - line-height: 26px; text-align: left; width: 100%; - text-overflow: ellipsis; - overflow: hidden; - margin-bottom: 24px; -} - -.description > a { - color: var(--primary); + margin-bottom: var(--unit-6); } /* Loading */ diff --git a/packages/web/src/components/link/ExternalLink.tsx b/packages/web/src/components/link/ExternalLink.tsx index 5fd81b309f..b0953cc61d 100644 --- a/packages/web/src/components/link/ExternalLink.tsx +++ b/packages/web/src/components/link/ExternalLink.tsx @@ -1,5 +1,45 @@ -type ExternalLinkProps = JSX.IntrinsicElements['a'] +import { MouseEvent, useCallback } from 'react' + +import { Name } from '@audius/common' + +import { make, useRecord } from 'common/store/analytics/actions' + +import { Link, LinkProps } from './Link' + +type ExternalLinkProps = LinkProps & { + source?: 'profile page' | 'track page' | 'collection page' +} export const ExternalLink = (props: ExternalLinkProps) => { - return + const { to, onClick, source, ...other } = props + + const record = useRecord() + + const handleClick = useCallback( + (event: MouseEvent) => { + onClick?.(event) + if (source) { + record( + make(Name.LINK_CLICKING, { + // @ts-expect-error + url: event.target.href, + source + }) + ) + } + }, + [onClick, record, source] + ) + + return ( + // @ts-expect-error + + ) } diff --git a/packages/web/src/components/track/GiantTrackTile.module.css b/packages/web/src/components/track/GiantTrackTile.module.css index 55b3103876..4a7d60c8e5 100644 --- a/packages/web/src/components/track/GiantTrackTile.module.css +++ b/packages/web/src/components/track/GiantTrackTile.module.css @@ -346,18 +346,10 @@ } .description { - padding-top: 13px; - white-space: pre-line; - color: var(--neutral); - font-size: var(--font-s); - font-weight: var(--font-medium); + padding-top: var(--unit-3); line-height: 26px; } -.description > a { - color: var(--primary); -} - .tagSection { padding-top: 13px; display: flex; diff --git a/packages/web/src/components/track/GiantTrackTile.tsx b/packages/web/src/components/track/GiantTrackTile.tsx index 3890a52232..aaa529912c 100644 --- a/packages/web/src/components/track/GiantTrackTile.tsx +++ b/packages/web/src/components/track/GiantTrackTile.tsx @@ -1,14 +1,11 @@ import { useCallback, useState } from 'react' import { - squashNewLines, getCanonicalName, formatDate, formatSeconds, Genre, FeatureFlags, - isAudiusUrl, - getPathFromAudiusUrl, Nullable, Remix, CoverArtSizes, @@ -28,7 +25,6 @@ import { IconKebabHorizontal } from '@audius/stems' import cn from 'classnames' -import Linkify from 'linkify-react' import { ReactComponent as IconRobot } from 'assets/img/robot.svg' import DownloadButtons from 'components/download-buttons/DownloadButtons' @@ -43,6 +39,7 @@ import { Tile } from 'components/tile' import Toast from 'components/toast/Toast' import Tooltip from 'components/tooltip/Tooltip' import { ComponentPlacement } from 'components/types' +import { UserGeneratedText } from 'components/user-generated-text' import { getFeatureEnabled } from 'services/remote-config/featureFlagHelpers' import { moodMap } from 'utils/Moods' @@ -61,7 +58,6 @@ const BUTTON_COLLAPSE_WIDTHS = { second: 1190, third: 1286 } - // Toast timeouts in ms const REPOST_TIMEOUT = 1000 const SAVED_TIMEOUT = 1000 @@ -106,8 +102,6 @@ export type GiantTrackTileProps = { onClickFavorites: () => void onClickReposts: () => void onDownload: (trackId: ID, category?: string, parentTrackId?: ID) => void - onExternalLinkClick: (event: React.MouseEvent) => void - onInternalLinkClick: (url: string) => void onMakePublic: (trackId: ID) => void onFollow: () => void onPlay: () => void @@ -153,9 +147,7 @@ export const GiantTrackTile = ({ onClickFavorites, onClickReposts, onDownload, - onExternalLinkClick, onFollow, - onInternalLinkClick, onMakePublic, onPlay, onSave, @@ -643,30 +635,13 @@ export const GiantTrackTile = ({ ) : null} {description ? ( - ) => { - const url = event.currentTarget.href - - if (isAudiusUrl(url)) { - const path = getPathFromAudiusUrl(url) - event.nativeEvent.preventDefault() - onInternalLinkClick(path ?? '/') - } else { - onExternalLinkClick(event) - } - } - }, - target: (href, type, tokens) => { - return isAudiusUrl(href) ? '' : '_blank' - } - }} + -

- {squashNewLines(description)} -

-
+ {description} + ) : null} {renderTags()} {renderDownloadButtons()} diff --git a/packages/web/src/components/typography/Text.tsx b/packages/web/src/components/typography/Text.tsx index c85d45a63a..1e8b08eaa3 100644 --- a/packages/web/src/components/typography/Text.tsx +++ b/packages/web/src/components/typography/Text.tsx @@ -1,4 +1,10 @@ -import { ComponentProps, ElementType, ReactNode } from 'react' +import { + ComponentProps, + ElementType, + ForwardedRef, + forwardRef, + ReactNode +} from 'react' import { ColorValue, @@ -19,32 +25,49 @@ type TextOwnProps = { variant?: TextVariant size?: TextSize strength?: TextStrength + component?: ElementType color?: ColorValue + innerRef?: ForwardedRef } export type TextProps = TextOwnProps & Omit, keyof TextOwnProps> -export const Text = ( - props: TextProps -) => { +export const Text = forwardRef(function Text< + TextComponentType extends ElementType = 'span' +>(props: TextProps, ref: ForwardedRef) { const { className, children, variant = 'body', - strength = 'default', - size = 'medium', - color = 'neutral', + strength: strengthProp, + size: sizeProp, + color: colorProp, + component, as, + innerRef, + style: styleProp, ...otherProps } = props - const Tag: ElementType = as ?? variantTagMap[variant][size] ?? 'p' + const strength = + strengthProp ?? (variant === 'inherit' ? undefined : 'default') + + const size = sizeProp ?? (variant === 'inherit' ? undefined : 'medium') + const color = colorProp ?? (variant === 'inherit' ? undefined : 'neutral') + + const Tag: ElementType = + as ?? component ?? (size ? variantTagMap[variant][size] ?? 'p' : 'p') - const styleObject: CSSCustomProperties = { - '--text-color': `var(${toCSSVariableName(color)})` - } + const style: CSSCustomProperties = color + ? { + ...styleProp, + '--text-color': `var(${toCSSVariableName(color)})` + } + : styleProp + + const rootClassName = color ? styles.root : undefined type TextClass = keyof typeof styles const variantClassNames = [ @@ -55,11 +78,12 @@ export const Text = ( return ( {children} ) -} +}) diff --git a/packages/web/src/components/typography/typography.module.css b/packages/web/src/components/typography/typography.module.css index 3533ab35b3..e8fc6eb4de 100644 --- a/packages/web/src/components/typography/typography.module.css +++ b/packages/web/src/components/typography/typography.module.css @@ -17,6 +17,14 @@ ``` */ +.inherit { + color: inherit; + font-size: inherit; + font-weight: inherit; + line-height: inherit; + text-decoration: inherit; +} + .root { color: var(--text-color); } @@ -221,11 +229,3 @@ color: var(--focus); text-decoration: underline !important; } - -.inherit { - color: inherit; - font-size: inherit; - font-weight: inherit; - line-height: inherit; - text-decoration: inherit; -} diff --git a/packages/web/src/components/user-generated-text/UserGeneratedText.module.css b/packages/web/src/components/user-generated-text/UserGeneratedText.module.css new file mode 100644 index 0000000000..b64ed6c28e --- /dev/null +++ b/packages/web/src/components/user-generated-text/UserGeneratedText.module.css @@ -0,0 +1,4 @@ +.root { + white-space: pre-line; + /* user-select: none; */ +} diff --git a/packages/web/src/components/user-generated-text/UserGeneratedText.tsx b/packages/web/src/components/user-generated-text/UserGeneratedText.tsx new file mode 100644 index 0000000000..85aa968e8d --- /dev/null +++ b/packages/web/src/components/user-generated-text/UserGeneratedText.tsx @@ -0,0 +1,94 @@ +import { forwardRef, useMemo, MouseEvent } from 'react' + +import { + getPathFromAudiusUrl, + isAudiusUrl, + squashNewLines +} from '@audius/common' +import cn from 'classnames' +import Linkify from 'linkify-react' +import { IntermediateRepresentation, Opts } from 'linkifyjs' + +import { ExternalLink, Link } from 'components/link' +import { Text } from 'components/typography' +import { TextProps } from 'components/typography/Text' + +import styles from './UserGeneratedText.module.css' + +type UserGeneratedTextProps = TextProps & { + linkSource?: 'profile page' | 'track page' | 'collection page' + onClickLink?: (event: MouseEvent) => void +} + +const formatExternalLink = (href: string) => { + const strippedHref = href.replace(/((?:https?):\/\/)|www./g, '') + return `https://${strippedHref}` +} + +const formatAudiusUrl = (href: string) => { + return getPathFromAudiusUrl(href) as string +} + +const renderLink = ({ attributes, content }: IntermediateRepresentation) => { + const { href, ...props } = attributes + + const isExternalLink = !isAudiusUrl(href) + const to = isExternalLink ? formatExternalLink(href) : formatAudiusUrl(href) + + const LinkComponent = isExternalLink ? ExternalLink : Link + + return ( + + {content} + + ) +} + +export const UserGeneratedText = forwardRef(function ( + props: UserGeneratedTextProps, + ref +) { + const { + children: childrenProp, + variant, + color, + size, + strength, + component, + className, + linkSource, + onClickLink, + ...other + } = props + + const options: Opts = useMemo( + () => ({ + render: renderLink, + attributes: { source: linkSource, onClick: onClickLink } + }), + [linkSource, onClickLink] + ) + + const children = + typeof childrenProp === 'string' + ? squashNewLines(childrenProp) + : childrenProp + + return ( + + {children} + + ) +}) diff --git a/packages/web/src/components/user-generated-text/index.ts b/packages/web/src/components/user-generated-text/index.ts new file mode 100644 index 0000000000..decfb4050e --- /dev/null +++ b/packages/web/src/components/user-generated-text/index.ts @@ -0,0 +1 @@ +export * from './UserGeneratedText' diff --git a/packages/web/src/pages/collection-page/CollectionPageProvider.tsx b/packages/web/src/pages/collection-page/CollectionPageProvider.tsx index 961d7af91c..cc18419194 100644 --- a/packages/web/src/pages/collection-page/CollectionPageProvider.tsx +++ b/packages/web/src/pages/collection-page/CollectionPageProvider.tsx @@ -485,16 +485,6 @@ class CollectionPage extends Component< } } - onClickDescriptionExternalLink = (event: any) => { - const { record } = this.props - record( - make(Name.LINK_CLICKING, { - url: event.target.href, - source: 'collection page' - }) - ) - } - onClickSave = (record: CollectionPageTrackRecord) => { if (!record.has_current_user_saved) { this.props.saveTrack(record.track_id) @@ -736,8 +726,7 @@ class CollectionPage extends Component< tracks, userId, userPlaylists, - smartCollection, - onClickDescriptionInternalLink + smartCollection } = this.props const { allowReordering } = this.state const { playlistId } = this.props @@ -784,8 +773,6 @@ class CollectionPage extends Component< onClickRow: this.onClickRow, onClickSave: this.onClickSave, onClickRepostTrack: this.onClickRepostTrack, - onClickDescriptionExternalLink: this.onClickDescriptionExternalLink, - onClickDescriptionInternalLink, onSortTracks: this.onSortTracks, onReorderTracks: this.onReorderTracks, onClickRemove: this.onClickRemove, @@ -1014,8 +1001,7 @@ function mapDispatchToProps(dispatch: Dispatch) { openEditCollectionModal({ collectionId, isCollectionViewed: true }) ), updatePlaylistLastViewedAt: (playlistId: ID) => - dispatch(updatedPlaylistViewed({ playlistId })), - onClickDescriptionInternalLink: (path: string) => dispatch(pushRoute(path)) + dispatch(updatedPlaylistViewed({ playlistId })) } } diff --git a/packages/web/src/pages/collection-page/components/desktop/CollectionPage.tsx b/packages/web/src/pages/collection-page/components/desktop/CollectionPage.tsx index 8107c3e276..3591433d09 100644 --- a/packages/web/src/pages/collection-page/components/desktop/CollectionPage.tsx +++ b/packages/web/src/pages/collection-page/components/desktop/CollectionPage.tsx @@ -93,8 +93,6 @@ export type CollectionPageProps = { ) => void onClickReposts?: () => void onClickFavorites?: () => void - onClickDescriptionExternalLink: (e: any) => void - onClickDescriptionInternalLink: (e: any) => void } const CollectionPage = ({ @@ -120,9 +118,7 @@ const CollectionPage = ({ onReorderTracks, onClickRemove, onClickReposts, - onClickFavorites, - onClickDescriptionExternalLink, - onClickDescriptionInternalLink + onClickFavorites }: CollectionPageProps) => { // TODO: Consider dynamic lineups, esp. for caching improvement. const { isEnabled: arePlaylistUpdatesEnabled } = useFlag( @@ -202,8 +198,6 @@ const CollectionPage = ({ onPlay={onPlay} onClickReposts={onClickReposts} onClickFavorites={onClickFavorites} - onClickDescriptionExternalLink={onClickDescriptionExternalLink} - onClickDescriptionInternalLink={onClickDescriptionInternalLink} // Smart collection variant={variant} gradient={gradient} diff --git a/packages/web/src/pages/profile-page/components/SocialLink.module.css b/packages/web/src/pages/profile-page/components/SocialLink.module.css index 4a89107bd9..a0f4fd6fc9 100644 --- a/packages/web/src/pages/profile-page/components/SocialLink.module.css +++ b/packages/web/src/pages/profile-page/components/SocialLink.module.css @@ -1,81 +1,18 @@ -.socialLink { - color: var(--neutral); - font-size: var(--font-s); - font-weight: var(--font-medium); - letter-spacing: 0.5px; - - margin-bottom: 12px; - user-select: none; -} - -.socialLink:hover { - color: inherit; -} - -.wrapper { +.root { display: inline-flex; - transition: all 0.18 ease-in-out; -} - -.wrapper.singleLink { - align-items: center; - cursor: pointer; + column-gap: var(--unit-2); } -.socialLink .icon { - width: 20px; - height: 20px; -} -.socialLink .icon.iconOnly { - width: 24px; - height: 24px; +.root:hover .icon path { + fill: var(--primary); } -.socialLink .text { - margin-left: 6px; +.text { max-width: 180px; - max-height: 40px; - line-height: 20px; text-overflow: ellipsis; overflow-x: hidden; } -.socialLink .singleLink .text { +.singleLink { white-space: nowrap; } - -.socialLink :not(.singleLink) .text span { - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; -} - -.icon path { - fill: var(--neutral); -} - -.wrapper.singleLink:hover { - color: var(--primary); - transform: scale(1.01); -} - -.wrapper.singleLink:hover .icon path { - fill: var(--primary); -} - -.wrapper.singleLink:active { - transform: scale(0.98); -} - -.wrapper a { - color: var(--primary); - display: inline-block; - max-width: 160px; - overflow-x: hidden; - text-overflow: ellipsis; - vertical-align: bottom; -} - -.wrapper a:hover { - text-decoration: underline !important; -} diff --git a/packages/web/src/pages/profile-page/components/SocialLink.tsx b/packages/web/src/pages/profile-page/components/SocialLink.tsx index 52478cafd5..5b838c9911 100644 --- a/packages/web/src/pages/profile-page/components/SocialLink.tsx +++ b/packages/web/src/pages/profile-page/components/SocialLink.tsx @@ -1,6 +1,5 @@ import { ReactNode } from 'react' -import { Maybe } from '@audius/common' import { IconTwitterBird, IconInstagram, @@ -9,10 +8,12 @@ import { IconTikTokInverted } from '@audius/stems' import cn from 'classnames' -import Linkify from 'linkify-react' +import { Icon } from 'components/Icon' import { ExternalLink } from 'components/link' import Tooltip from 'components/tooltip/Tooltip' +import { Text } from 'components/typography' +import { UserGeneratedText } from 'components/user-generated-text' import styles from './SocialLink.module.css' @@ -41,6 +42,14 @@ const singleLinkTypes = [ Type.WEBSITE ] +const socialIcons = { + [Type.TWITTER]: IconTwitterBird, + [Type.INSTAGRAM]: IconInstagram, + [Type.TIKTOK]: IconTikTokInverted, + [Type.WEBSITE]: IconLink, + [Type.DONATION]: IconDonate +} + const isHandleType = (type: Type): type is HandleType => handleTypes.includes(type) @@ -55,39 +64,17 @@ const SocialLink = (props: SocialLinkProps) => { const { type, link, onClick, iconOnly = false } = props const isSingleLink = singleLinkTypes.includes(type) - let icon: ReactNode - switch (type) { - case Type.TWITTER: - icon = ( - - ) - break - case Type.INSTAGRAM: - icon = ( - - ) - break - case Type.TIKTOK: - icon = ( - - ) - break - case Type.WEBSITE: - icon = - break - case Type.DONATION: - icon = ( - - - - ) - break + const SocialIcon = socialIcons[type] + + let icon = ( + + ) + if (type === Type.DONATION) { + icon = {icon} } let text: ReactNode @@ -96,23 +83,11 @@ const SocialLink = (props: SocialLinkProps) => { } else { text = link.replace(/((?:https?):\/\/)|www./g, '') if (type === Type.DONATION) { - text = ( - - {text} - - ) + text = {text} } } - let href: Maybe + let href = '' if (isHandleType(type)) { href = `${SITE_URL_MAP[type]}${link}` @@ -124,28 +99,21 @@ const SocialLink = (props: SocialLinkProps) => { } } - const socialLinkContent = ( -
- {icon} - {!iconOnly &&
{text}
} -
- ) - - const rootProps = { - onClick, - className: styles.socialLink - } + const Root = href ? ExternalLink : Text - return href ? ( - - {socialLinkContent} - - ) : ( -
{socialLinkContent}
+ return ( + + {icon} + {iconOnly ? null : ( + + {text} + + )} + ) } diff --git a/packages/web/src/pages/profile-page/components/desktop/ProfileBio.tsx b/packages/web/src/pages/profile-page/components/desktop/ProfileBio.tsx index 26b12b9e19..ecac854428 100644 --- a/packages/web/src/pages/profile-page/components/desktop/ProfileBio.tsx +++ b/packages/web/src/pages/profile-page/components/desktop/ProfileBio.tsx @@ -1,16 +1,8 @@ import { useCallback, useEffect, useState } from 'react' -import { - Name, - getPathFromAudiusUrl, - isAudiusUrl, - squashNewLines -} from '@audius/common' +import { Name } from '@audius/common' import { ResizeObserver } from '@juggle/resize-observer' import cn from 'classnames' -import { push as pushRoute } from 'connected-react-router' -import Linkify from 'linkify-react' -import { useDispatch } from 'react-redux' // eslint-disable-next-line no-restricted-imports -- TODO: migrate to @react-spring/web import { animated } from 'react-spring' import useMeasure from 'react-use-measure' @@ -19,6 +11,7 @@ import { ReactComponent as IconCaretDownLine } from 'assets/img/iconCaretDownLin import { ReactComponent as IconCaretUpLine } from 'assets/img/iconCaretUpLine.svg' import { make, useRecord } from 'common/store/analytics/actions' import { OpacityTransition } from 'components/transition-container/OpacityTransition' +import { UserGeneratedText } from 'components/user-generated-text' import SocialLink, { Type } from '../SocialLink' @@ -55,7 +48,6 @@ export const ProfileBio = ({ instagramHandle, tikTokHandle }: ProfileBioProps) => { - const dispatch = useDispatch() const [isCollapsed, setIsCollapsed] = useState(false) const [isCollapsible, setIsCollapsible] = useState(false) const [bioRef, { height: bioSize }] = useMeasure({ @@ -100,24 +92,6 @@ export const ProfileBio = ({ const record = useRecord() - const onExternalLinkClick = useCallback( - (event: React.MouseEvent) => { - record( - make(Name.LINK_CLICKING, { - url: event.currentTarget.href, - source: 'profile page' - }) - ) - }, - [record] - ) - const onInternalLinkClick = useCallback( - (url: string) => { - dispatch(pushRoute(url)) - }, - [dispatch] - ) - const onClickTwitter = useCallback(() => { record( make(Name.PROFILE_PAGE_CLICK_TWITTER, { @@ -246,35 +220,16 @@ export const ProfileBio = ({ return (
- ) => { - const url = event.currentTarget.innerText - - if (isAudiusUrl(url)) { - const path = getPathFromAudiusUrl(url) - event.nativeEvent.preventDefault() - onInternalLinkClick(path ?? '/') - } else { - onExternalLinkClick(event) - } - } - }, - target: (href, type, tokens) => { - return isAudiusUrl(href) ? '' : '_blank' - } - }} + -
- {squashNewLines(bio)} -
-
+ {bio} + {isCollapsed ? (
diff --git a/packages/web/src/pages/profile-page/components/desktop/ProfilePage.module.css b/packages/web/src/pages/profile-page/components/desktop/ProfilePage.module.css index 9a93c96049..cae5a2af40 100644 --- a/packages/web/src/pages/profile-page/components/desktop/ProfilePage.module.css +++ b/packages/web/src/pages/profile-page/components/desktop/ProfilePage.module.css @@ -87,7 +87,6 @@ margin-bottom: 14px; } -.info .description, .info .location, .info .joined { color: var(--neutral); @@ -97,12 +96,12 @@ user-select: none; } -.info .description { - margin-bottom: 16px; - white-space: pre-line; +.description { + margin-bottom: var(--unit-4); overflow: hidden; text-overflow: ellipsis; } + /* truncates the content at a maximum of 4 lines */ /* works on modern browsers */ .truncated { @@ -113,13 +112,11 @@ line-clamp: 4; -webkit-box-orient: vertical; } -.info .description > a { - color: var(--primary); -} .socials { display: flex; flex-direction: column; + row-gap: var(--unit-4); padding-bottom: var(--unit-2); } .info .socialsTruncated { diff --git a/packages/web/src/pages/profile-page/components/mobile/ProfileHeader.module.css b/packages/web/src/pages/profile-page/components/mobile/ProfileHeader.module.css index b33a77891d..4252925da4 100644 --- a/packages/web/src/pages/profile-page/components/mobile/ProfileHeader.module.css +++ b/packages/web/src/pages/profile-page/components/mobile/ProfileHeader.module.css @@ -190,25 +190,14 @@ } .bio { - flex: 1; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; - white-space: pre-line; + text-align: left; width: 100%; - margin-top: 14px; - color: var(--neutral-light-2); - font-family: var(--font-family); - font-size: var(--font-s); + margin-top: var(--unit-2); transition: all 0.07s ease-in-out; - font-weight: var(--font-medium); - line-height: 20px; - text-align: left; -} - -.bio > a { - color: var(--primary); } .bio:not(.bioExpanded) { diff --git a/packages/web/src/pages/profile-page/components/mobile/ProfileHeader.tsx b/packages/web/src/pages/profile-page/components/mobile/ProfileHeader.tsx index 93ae99e2ae..1ce73f9801 100644 --- a/packages/web/src/pages/profile-page/components/mobile/ProfileHeader.tsx +++ b/packages/web/src/pages/profile-page/components/mobile/ProfileHeader.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useCallback, useEffect } from 'react' +import { useState, useRef, useCallback, useEffect, MouseEvent } from 'react' import { ID, @@ -8,7 +8,6 @@ import { WidthSizes, SquareSizes, formatCount, - squashNewLines, imageCoverPhotoBlank } from '@audius/common' import { @@ -22,10 +21,10 @@ import { IconTikTok } from '@audius/stems' import cn from 'classnames' -import Linkify from 'linkify-react' import { ReactComponent as BadgeArtist } from 'assets/img/badgeArtist.svg' import { make, useRecord } from 'common/store/analytics/actions' +import { Icon } from 'components/Icon' import { ArtistRecommendationsDropdown } from 'components/artist-recommendations/ArtistRecommendationsDropdown' import DynamicImage from 'components/dynamic-image/DynamicImage' import { FollowButton } from 'components/follow-button/FollowButton' @@ -34,6 +33,7 @@ import SubscribeButton from 'components/subscribe-button/SubscribeButton' import FollowsYouBadge from 'components/user-badges/FollowsYouBadge' import ProfilePageBadge from 'components/user-badges/ProfilePageBadge' import UserBadges from 'components/user-badges/UserBadges' +import { UserGeneratedText } from 'components/user-generated-text' import { useUserCoverPhoto } from 'hooks/useUserCoverPhoto' import { useUserProfilePicture } from 'hooks/useUserProfilePicture' import { FOLLOWING_USERS_ROUTE, FOLLOWERS_USERS_ROUTE } from 'utils/route' @@ -244,18 +244,6 @@ const ProfileHeader = ({ ) }, [record, tikTokHandle, handle]) - const onExternalLinkClick = useCallback( - (event: { target: { href: string } }) => { - record( - make(Name.LINK_CLICKING, { - url: event.target.href, - source: 'profile page' as const - }) - ) - }, - [record] - ) - const onGoToFollowersPage = () => { setFollowersUserId(userId) goToRoute(FOLLOWERS_USERS_ROUTE) @@ -281,11 +269,12 @@ const ProfileHeader = ({ ) } - const onDonationLinkClick = useCallback( - (event: { target: { href: string } }) => { + const handleClickDonationLink = useCallback( + (event: MouseEvent) => { record( make(Name.PROFILE_PAGE_CLICK_DONATION, { handle, + // @ts-expect-error donation: event.target.href }) ) @@ -415,60 +404,57 @@ const ProfileHeader = ({ /> {twitterHandle ? ( } /> ) : null} {instagramHandle ? ( } /> ) : null} {tikTokHandle ? ( } /> ) : null}
+ {bio ? ( - -

- {squashNewLines(bio)} -

-
+ + {bio} + ) : null} {hasEllipsis && !isDescriptionMinimized && (website || donation) && (
{website && (
- + {website}
)} {donation && (
- - - - {donation} - - + + + {donation} +
)}
diff --git a/packages/web/src/pages/profile-page/components/mobile/SocialLink.tsx b/packages/web/src/pages/profile-page/components/mobile/SocialLink.tsx index 7b0ad57a06..fad8c2c32f 100644 --- a/packages/web/src/pages/profile-page/components/mobile/SocialLink.tsx +++ b/packages/web/src/pages/profile-page/components/mobile/SocialLink.tsx @@ -6,7 +6,7 @@ import styles from './ProfileHeader.module.css' type SocialLinkProps = { onClick: () => void - href: string + to: string icon: ReactElement } diff --git a/packages/web/src/pages/track-page/TrackPageProvider.tsx b/packages/web/src/pages/track-page/TrackPageProvider.tsx index 411fa46090..a10f7c5854 100644 --- a/packages/web/src/pages/track-page/TrackPageProvider.tsx +++ b/packages/web/src/pages/track-page/TrackPageProvider.tsx @@ -383,9 +383,7 @@ class TrackPageProvider extends Component< buffering, userId, pause, - downloadTrack, - onExternalLinkClick, - onInternalLinkClick + downloadTrack } = this.props const heroPlaying = playing && @@ -476,9 +474,7 @@ class TrackPageProvider extends Component< isPlaying: playing, isBuffering: buffering, play: this.onMoreByArtistTracksPlay, - pause, - onExternalLinkClick, - onInternalLinkClick + pause } return ( @@ -608,16 +604,6 @@ function mapDispatchToProps(dispatch: Dispatch) { dispatch(setRepost(trackId, RepostType.TRACK)), setFavoriteTrackId: (trackId: ID) => dispatch(setFavorite(trackId, FavoriteType.TRACK)), - onInternalLinkClick: (route: string) => { - dispatch(pushRoute(route)) - }, - onExternalLinkClick: (event: any) => { - const trackEvent: TrackEvent = make(Name.LINK_CLICKING, { - url: event.target.href, - source: 'track page' as const - }) - dispatch(trackEvent) - }, record: (event: TrackEvent) => dispatch(event), setRepostUsers: (trackID: ID) => dispatch( diff --git a/packages/web/src/pages/track-page/components/desktop/TrackPage.tsx b/packages/web/src/pages/track-page/components/desktop/TrackPage.tsx index f39447110b..8db2fb5a03 100644 --- a/packages/web/src/pages/track-page/components/desktop/TrackPage.tsx +++ b/packages/web/src/pages/track-page/components/desktop/TrackPage.tsx @@ -63,8 +63,6 @@ export type OwnProps = { isBuffering: boolean play: (uid?: string) => void pause: () => void - onExternalLinkClick: (event: React.MouseEvent) => void - onInternalLinkClick: (url: string) => void } const TrackPage = ({ @@ -89,8 +87,6 @@ const TrackPage = ({ onUnfollow, onDownloadTrack, makePublic, - onExternalLinkClick, - onInternalLinkClick, onClickReposts, onClickFavorites, @@ -152,8 +148,6 @@ const TrackPage = ({ } isSaved={isSaved} badge={badge} - onExternalLinkClick={onExternalLinkClick} - onInternalLinkClick={onInternalLinkClick} isUnlisted={defaults.isUnlisted} isPremium={defaults.isPremium} premiumConditions={defaults.premiumConditions} diff --git a/packages/web/src/pages/track-page/components/mobile/TrackHeader.module.css b/packages/web/src/pages/track-page/components/mobile/TrackHeader.module.css index ee94c46754..eae718e380 100644 --- a/packages/web/src/pages/track-page/components/mobile/TrackHeader.module.css +++ b/packages/web/src/pages/track-page/components/mobile/TrackHeader.module.css @@ -84,19 +84,19 @@ width: 100%; } +.withSectionDivider { + width: 100%; + padding-top: var(--unit-4); + border-top: 1px solid var(--neutral-light-9); +} + .description { - color: var(--neutral); + composes: withSectionDivider; text-align: left; white-space: pre-line; - text-overflow: ellipsis; - overflow: hidden; width: 100%; } -.description > a { - color: var(--primary); -} - /* Moible Toast */ .mobileToast { top: 56px !important; @@ -264,9 +264,3 @@ display: flex; justify-content: center; } - -.withSectionDivider { - width: 100%; - padding-top: var(--unit-4); - border-top: 1px solid var(--neutral-light-9); -} diff --git a/packages/web/src/pages/track-page/components/mobile/TrackHeader.tsx b/packages/web/src/pages/track-page/components/mobile/TrackHeader.tsx index f34944a0f1..1eef0d63dd 100644 --- a/packages/web/src/pages/track-page/components/mobile/TrackHeader.tsx +++ b/packages/web/src/pages/track-page/components/mobile/TrackHeader.tsx @@ -2,12 +2,10 @@ import { useCallback } from 'react' import { ID, - Name, SquareSizes, CoverArtSizes, FieldVisibility, Remix, - squashNewLines, getCanonicalName, formatSeconds, formatDate, @@ -30,10 +28,8 @@ import { IconSpecialAccess } from '@audius/stems' import cn from 'classnames' -import Linkify from 'linkify-react' import { ReactComponent as IconRobot } from 'assets/img/robot.svg' -import { make, useRecord } from 'common/store/analytics/actions' import CoSign from 'components/co-sign/CoSign' import HoverInfo from 'components/co-sign/HoverInfo' import { Size } from 'components/co-sign/types' @@ -45,7 +41,7 @@ import { SearchTag } from 'components/search/SearchTag' import { AiTrackSection } from 'components/track/AiTrackSection' import Badge from 'components/track/Badge' import { PremiumTrackSection } from 'components/track/PremiumTrackSection' -import typeStyles from 'components/typography/typography.module.css' +import { UserGeneratedText } from 'components/user-generated-text' import { useTrackCoverArt } from 'hooks/useTrackCoverArt' import { moodMap } from 'utils/Moods' import { isDarkMode } from 'utils/theme/theme' @@ -224,19 +220,6 @@ const TrackHeader = ({ { label: 'Credit', value: credits } ].filter(({ isHidden, value }) => !isHidden && !!value) - const record = useRecord() - const onExternalLinkClick = useCallback( - (event: { target: { href: string } }) => { - record( - make(Name.LINK_CLICKING, { - url: event.target.href, - source: 'track page' as const - }) - ) - }, - [record] - ) - const onClickOverflow = () => { const overflowActions = [ isOwner || !showSocials @@ -470,18 +453,12 @@ const TrackHeader = ({ /> ) : null} {description ? ( - -

- {squashNewLines(description)} -

-
+ + {description} + ) : null}
{renderTrackLabels()}