diff --git a/.changeset/brave-geese-clap.md b/.changeset/brave-geese-clap.md new file mode 100644 index 0000000..a7a25cf --- /dev/null +++ b/.changeset/brave-geese-clap.md @@ -0,0 +1,5 @@ +--- +'react-tweet': patch +--- + +Updated Twitter theme diff --git a/packages/react-tweet/src/api/types/user.ts b/packages/react-tweet/src/api/types/user.ts index 4d0e9ff..18740a0 100644 --- a/packages/react-tweet/src/api/types/user.ts +++ b/packages/react-tweet/src/api/types/user.ts @@ -2,8 +2,9 @@ export interface TweetUser { id_str: string name: string profile_image_url_https: string + profile_image_shape: 'Circle' | 'Square' screen_name: string verified: boolean - verified_type: string + verified_type?: 'Business' | 'Government' is_blue_verified: boolean } diff --git a/packages/react-tweet/src/twitter-theme/components.tsx b/packages/react-tweet/src/twitter-theme/components.tsx index 528cfa8..90a4111 100644 --- a/packages/react-tweet/src/twitter-theme/components.tsx +++ b/packages/react-tweet/src/twitter-theme/components.tsx @@ -1,4 +1,5 @@ export * from './types.js' +export * from './icons/index.js' export * from './embedded-tweet.js' export * from './tweet-actions-copy.js' export * from './tweet-actions.js' diff --git a/packages/react-tweet/src/twitter-theme/icons/icons.module.css b/packages/react-tweet/src/twitter-theme/icons/icons.module.css new file mode 100644 index 0000000..aca493e --- /dev/null +++ b/packages/react-tweet/src/twitter-theme/icons/icons.module.css @@ -0,0 +1,9 @@ +.verified { + margin-left: 0.125rem; + max-width: 20px; + max-height: 20px; + height: 1.25em; + fill: currentColor; + user-select: none; + vertical-align: text-bottom; +} diff --git a/packages/react-tweet/src/twitter-theme/icons/index.ts b/packages/react-tweet/src/twitter-theme/icons/index.ts new file mode 100644 index 0000000..067f6c9 --- /dev/null +++ b/packages/react-tweet/src/twitter-theme/icons/index.ts @@ -0,0 +1,3 @@ +export * from './verified.js' +export * from './verified-business.js' +export * from './verified-government.js' diff --git a/packages/react-tweet/src/twitter-theme/icons/verified-business.tsx b/packages/react-tweet/src/twitter-theme/icons/verified-business.tsx new file mode 100644 index 0000000..a732598 --- /dev/null +++ b/packages/react-tweet/src/twitter-theme/icons/verified-business.tsx @@ -0,0 +1,53 @@ +import s from './icons.module.css' + +export const VerifiedBusiness = () => ( + + + + + + + + + + + + + + + + + + + + + +) diff --git a/packages/react-tweet/src/twitter-theme/icons/verified-government.tsx b/packages/react-tweet/src/twitter-theme/icons/verified-government.tsx new file mode 100644 index 0000000..7a17108 --- /dev/null +++ b/packages/react-tweet/src/twitter-theme/icons/verified-government.tsx @@ -0,0 +1,18 @@ +import s from './icons.module.css' + +export const VerifiedGovernment = () => ( + + + + + +) diff --git a/packages/react-tweet/src/twitter-theme/icons/verified.tsx b/packages/react-tweet/src/twitter-theme/icons/verified.tsx new file mode 100644 index 0000000..d5c8e8d --- /dev/null +++ b/packages/react-tweet/src/twitter-theme/icons/verified.tsx @@ -0,0 +1,14 @@ +import s from './icons.module.css' + +export const Verified = () => ( + + + + + +) diff --git a/packages/react-tweet/src/twitter-theme/theme.css b/packages/react-tweet/src/twitter-theme/theme.css index d1fa3b3..d176b10 100644 --- a/packages/react-tweet/src/twitter-theme/theme.css +++ b/packages/react-tweet/src/twitter-theme/theme.css @@ -53,6 +53,7 @@ --tweet-bg-color: #fff; --tweet-bg-color-hover: rgb(247, 249, 249); --tweet-color-blue-primary: rgb(29, 155, 240); + --tweet-color-blue-primary-hover: rgb(26, 140, 216); --tweet-color-blue-secondary: rgb(0, 111, 214); --tweet-color-blue-secondary-hover: rgba(0, 111, 214, 0.1); --tweet-color-red-primary: rgb(249, 24, 128); @@ -80,6 +81,7 @@ --tweet-bg-color: rgb(21, 32, 43); --tweet-bg-color-hover: rgb(30, 39, 50); --tweet-color-blue-primary: rgb(29, 155, 240); + --tweet-color-blue-primary-hover: rgb(26, 140, 216); --tweet-color-blue-secondary: rgb(107, 201, 251); --tweet-color-blue-secondary-hover: rgba(107, 201, 251, 0.1); --tweet-color-red-primary: rgb(249, 24, 128); @@ -108,6 +110,7 @@ --tweet-bg-color: rgb(21, 32, 43); --tweet-bg-color-hover: rgb(30, 39, 50); --tweet-color-blue-primary: rgb(29, 155, 240); + --tweet-color-blue-primary-hover: rgb(26, 140, 216); --tweet-color-blue-secondary: rgb(107, 201, 251); --tweet-color-blue-secondary-hover: rgba(107, 201, 251, 0.1); --tweet-color-red-primary: rgb(249, 24, 128); diff --git a/packages/react-tweet/src/twitter-theme/tweet-header.module.css b/packages/react-tweet/src/twitter-theme/tweet-header.module.css index ad81cce..243ea46 100644 --- a/packages/react-tweet/src/twitter-theme/tweet-header.module.css +++ b/packages/react-tweet/src/twitter-theme/tweet-header.module.css @@ -20,6 +20,9 @@ overflow: hidden; border-radius: 9999px; } +.avatarSquare { + border-radius: 4px; +} .avatarShadow { height: 100%; width: 100%; @@ -56,21 +59,16 @@ overflow: hidden; white-space: nowrap; } -.authorVerifiedIcon { - margin-left: 0.125rem; - max-width: 20px; - max-height: 20px; - height: 1.25em; - fill: currentColor; - user-select: none; - vertical-align: text-bottom; -} .verifiedOld { color: var(--tweet-verified-old-color); } .verifiedBlue { color: var(--tweet-verified-blue-color); } +.verifiedGovernment { + /* color: var(--tweet-verified-government-color); */ + color: rgb(130, 154, 171); +} .authorMeta { display: flex; } diff --git a/packages/react-tweet/src/twitter-theme/tweet-header.tsx b/packages/react-tweet/src/twitter-theme/tweet-header.tsx index f2b8393..bcf7b2a 100644 --- a/packages/react-tweet/src/twitter-theme/tweet-header.tsx +++ b/packages/react-tweet/src/twitter-theme/tweet-header.tsx @@ -2,6 +2,11 @@ import clsx from 'clsx' import type { EnrichedTweet } from '../utils.js' import type { TwitterComponents } from './types.js' import { AvatarImg } from './avatar-img.js' +import { + Verified, + VerifiedGovernment, + VerifiedBusiness, +} from './icons/index.js' import s from './tweet-header.module.css' type Props = { @@ -11,6 +16,26 @@ type Props = { export const TweetHeader = ({ tweet, components }: Props) => { const Img = components?.AvatarImg ?? AvatarImg + const { user } = tweet + const verified = user.verified || user.is_blue_verified || user.verified_type + let icon = + let iconClassName: string | null = s.verifiedBlue + + if (verified) { + if (!user.is_blue_verified) { + iconClassName = s.verifiedOld + } + switch (user.verified_type) { + case 'Government': + icon = + iconClassName = s.verifiedGovernment + break + case 'Business': + icon = + iconClassName = null + break + } + } return (
@@ -20,10 +45,15 @@ export const TweetHeader = ({ tweet, components }: Props) => { target="_blank" rel="noopener noreferrer" > -
+
{tweet.user.name} @@ -40,28 +70,11 @@ export const TweetHeader = ({ tweet, components }: Props) => { rel="noopener noreferrer" >
- {tweet.user.name} + {user.name}
- {tweet.user.verified || - (tweet.user.is_blue_verified && ( -
- - - - - -
- ))} + {verified && ( +
{icon}
+ )}
{ target="_blank" rel="noopener noreferrer" > - - @{tweet.user.screen_name} - + @{user.screen_name}
ยท a { + min-width: 2rem; + min-height: 2rem; + font-size: 0.875rem; + line-height: 1rem; + backdrop-filter: blur(4px); + background-color: rgba(15, 20, 25, 0.75); +} +.watchOnTwitter > a:hover { + background-color: rgba(39, 44, 48, 0.75); +} +.viewReplies { + position: relative; + min-height: 2rem; + background-color: var(--tweet-color-blue-primary); + border-color: var(--tweet-color-blue-primary); + font-size: 0.9375rem; + line-height: 1.25rem; +} +.viewReplies:hover { + background-color: var(--tweet-color-blue-primary-hover); +} diff --git a/packages/react-tweet/src/twitter-theme/tweet-media-video.tsx b/packages/react-tweet/src/twitter-theme/tweet-media-video.tsx index 9cf016a..dee4932 100644 --- a/packages/react-tweet/src/twitter-theme/tweet-media-video.tsx +++ b/packages/react-tweet/src/twitter-theme/tweet-media-video.tsx @@ -1,18 +1,23 @@ 'use client' import { useState } from 'react' +import clsx from 'clsx' import type { MediaAnimatedGif, MediaVideo } from '../api/index.js' -import { getMediaUrl, getMp4Video } from '../utils.js' +import { type EnrichedTweet, getMediaUrl, getMp4Video } from '../utils.js' import mediaStyles from './tweet-media.module.css' import s from './tweet-media-video.module.css' type Props = { + tweet: EnrichedTweet media: MediaAnimatedGif | MediaVideo } -export const TweetMediaVideo = ({ media }: Props) => { +export const TweetMediaVideo = ({ tweet, media }: Props) => { const [playButton, setPlayButton] = useState(true) + const [isPlaying, setIsPlaying] = useState(false) + const [ended, setEnded] = useState(false) const mp4Video = getMp4Video(media) + let timeout = 0 return ( <> @@ -20,10 +25,26 @@ export const TweetMediaVideo = ({ media }: Props) => { className={mediaStyles.image} poster={getMediaUrl(media, 'small')} controls={!playButton} - draggable muted preload="metadata" tabIndex={playButton ? -1 : 0} + onPlay={() => { + if (timeout) window.clearTimeout(timeout) + if (!isPlaying) setIsPlaying(true) + if (ended) setEnded(false) + }} + onPause={() => { + // When the video is seeked (moved to a different timestamp), it will pause for a moment + // before resuming. We don't want to show the message in that case so we wait a bit. + if (timeout) window.clearTimeout(timeout) + timeout = window.setTimeout(() => { + if (isPlaying) setIsPlaying(false) + timeout = 0 + }, 100) + }} + onEnded={() => { + setEnded(true) + }} > @@ -38,6 +59,7 @@ export const TweetMediaVideo = ({ media }: Props) => { e.preventDefault() setPlayButton(false) + setIsPlaying(true) video.play() video.focus() }} @@ -53,6 +75,30 @@ export const TweetMediaVideo = ({ media }: Props) => { )} + + {!isPlaying && !ended && ( + + )} + + {ended && ( + + View replies + + )} ) } diff --git a/packages/react-tweet/src/twitter-theme/tweet-media.module.css b/packages/react-tweet/src/twitter-theme/tweet-media.module.css index c6f5464..f8ccca6 100644 --- a/packages/react-tweet/src/twitter-theme/tweet-media.module.css +++ b/packages/react-tweet/src/twitter-theme/tweet-media.module.css @@ -37,7 +37,7 @@ outline-style: none; } .skeleton { - padding-bottom: 56.3542%; + padding-bottom: 56.25%; width: 100%; display: block; } diff --git a/packages/react-tweet/src/twitter-theme/tweet-media.tsx b/packages/react-tweet/src/twitter-theme/tweet-media.tsx index b06a41c..9b19361 100644 --- a/packages/react-tweet/src/twitter-theme/tweet-media.tsx +++ b/packages/react-tweet/src/twitter-theme/tweet-media.tsx @@ -1,3 +1,4 @@ +import { Fragment } from 'react' import clsx from 'clsx' import { type EnrichedTweet, getMediaUrl } from '../utils.js' import type { TwitterComponents } from './types.js' @@ -16,38 +17,52 @@ export const TweetMedia = ({ tweet, components }: Props) => { return (
-
-
1 && s.grid2Columns, - length === 3 && s.grid3, - length > 4 && s.grid2x2 - )} - > - {tweet.mediaDetails?.map((media) => - media.type === 'photo' ? ( - - {media.ext_alt_text - - ) : ( -
- -
- ) - )} -
+ {tweet.mediaDetails?.map((media) => ( + +
+
1 && s.grid2Columns, + length === 3 && s.grid3, + length > 4 && s.grid2x2 + )} + > + {media.type === 'photo' ? ( + + {media.ext_alt_text + + ) : ( +
+ +
+ )} +
+ + ))}
) }