Skip to content

Commit 7a92646

Browse files
authored
Make react-tweet more customizable (#86)
* Update dependencies for react-tweet * Expose getEntities and getEntityText * Strict props in tweet-link * Expose useMp4Video * Removed unrequired use of useMemo * Export the useTweet hook * Moved components to twitter theme * moved useMounted to hooks * Updated imports in twitter theme * Export twitter theme components * Component updates * Export hooks and utils * Improve loading state for SWR tweet * Fixed import * Added custom-tweet-dub * Replaced video hook with util * Updated TweetProps type * Removed unused imports * Removed unused code * Make dub tweet a server component * Updated lock * Updated readme * Updated getEntities utility * Added a more powerful util * Use the getTweetData util in SWR * Updated custom tweet * Renamed TweetData with EnrichedTweet * Added nextra docs theme * Added initial docs to the nextra site * Added docs to the API types * Added API reference docs * Added Twitter theme docs * Added docs for the Next.js usage * Added docs for CRA usage * Added docs for Vite usage * Updated docs site * Updated pnpm-lock.yaml * Removed unnecessary pnpm-lock.yaml * Updated SEO * Added custom theme docs * Updated twitter theme docs * Updated framework docs and readme * Updated main readme and added contributing docs * documentation updates and fixes * Added demo link * Added fetchOptions support * Added changeset * Updated intro
1 parent d3cf679 commit 7a92646

File tree

116 files changed

+6999
-3684
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+6999
-3684
lines changed

.changeset/brave-pumpkins-hang.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'react-tweet': major
3+
---
4+
5+
Theme support

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ node_modules
1111
# Next.js
1212
.next
1313
out
14+
next-env.d.ts
1415

1516
# Production
1617
build

apps/create-react-app/.env

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
BROWSER = none
2-
PORT = 3001
2+
PORT = 3002

apps/create-react-app/readme.md

+2-32
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,3 @@
1-
# react-tweet for create-react-app
1+
# react-tweet for Create React App
22

3-
## Installation
4-
5-
Follow the [installation docs in the main README](/readme.md#installation).
6-
7-
## Usage
8-
9-
In any component, import [`Tweet`](/readme.md#tweet) from `react-tweet` and use it like so:
10-
11-
```tsx
12-
import { Tweet } from 'react-tweet'
13-
14-
export default function App() {
15-
return <Tweet id="1628832338187636740" />
16-
}
17-
```
18-
19-
To see the code in action go to: [/apps/create-react-app/src/app.js](/apps/create-react-app/src/app.js).
20-
21-
You can learn more about `react-tweet` in the [API Reference](/readme.md#api-reference).
22-
23-
## Running the test app
24-
25-
Clone this repository and run the following commands:
26-
27-
```bash
28-
pnpm install && pnpm dev --filter=create-react-app...
29-
```
30-
31-
The app will be up and running at http://localhost:3001.
32-
33-
`react-tweet` is imported from the root directory, so you can make changes to the package and see the changes reflected in the app immediately.
3+
Follow the instructions in the [official docs](https://react-tweet.vercel.app/create-react-app) to learn more about `react-tweet` for Create React App.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"typescript.tsdk": "../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib",
3+
"typescript.enablePromptUseWorkspaceTsdk": true
4+
}

apps/custom-tweet-dub/app/layout.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { FC, ReactNode } from 'react'
2+
import '../base.css'
3+
4+
const RootLayout: FC<{ children: ReactNode }> = ({ children }) => (
5+
<html>
6+
<head></head>
7+
<body>{children}</body>
8+
</html>
9+
)
10+
11+
export default RootLayout
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { getTweet } from 'react-tweet/api'
2+
import { Tweet } from '@/components/tweet'
3+
4+
type Props = {
5+
params: { tweet: string }
6+
}
7+
8+
export const revalidate = 1800
9+
10+
export async function generateMetadata({ params }: Props) {
11+
const tweet = await getTweet(params.tweet).catch(() => undefined)
12+
13+
if (!tweet) return { title: 'Next Tweet' }
14+
15+
const username = ` - @${tweet.user.screen_name}`
16+
const maxLength = 68 - username.length
17+
const text =
18+
tweet.text.length > maxLength
19+
? `${tweet.text.slice(0, maxLength)}…`
20+
: tweet.text
21+
22+
return { title: `${text}${username}` }
23+
}
24+
25+
const Page = ({ params }: Props) => <Tweet id={params.tweet} />
26+
27+
export default Page
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.root {
2+
font-family: var(--tweet-font-family);
3+
color: var(--tweet-font-color);
4+
background: var(--tweet-bg-color);
5+
height: 100vh;
6+
overflow: auto;
7+
padding: 2rem 1rem;
8+
}
9+
.main {
10+
display: flex;
11+
justify-content: center;
12+
}
13+
.footer {
14+
font-size: 0.875rem;
15+
text-align: center;
16+
margin-top: -0.5rem;
17+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { ReactNode } from 'react'
2+
import s from './layout.module.css'
3+
4+
const Layout = ({ children }: { children: ReactNode }) => (
5+
<div data-theme="light">
6+
<div className={s.root}>
7+
<main className={s.main}>
8+
<div className="max-w-[550px] min-w-[250px]">{children}</div>
9+
</main>
10+
</div>
11+
</div>
12+
)
13+
14+
export default Layout
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Post from './post.mdx'
2+
3+
export default function Page() {
4+
return <Post />
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Tweet } from '@/components/tweet'
2+
3+
<Tweet id={'1629307668568633344'} />

apps/custom-tweet-dub/base.css

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
*,
6+
*::before,
7+
*::after {
8+
box-sizing: inherit;
9+
}
10+
html {
11+
height: 100%;
12+
box-sizing: border-box;
13+
}
14+
body {
15+
position: relative;
16+
min-height: 100%;
17+
margin: 0;
18+
line-height: 1.65;
19+
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
20+
font-weight: 400;
21+
text-rendering: optimizeLegibility;
22+
-webkit-font-smoothing: antialiased;
23+
-moz-osx-font-smoothing: grayscale;
24+
scroll-behavior: smooth;
25+
}
26+
html,
27+
body {
28+
background: #fff;
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use client'
2+
3+
import Image, { ImageProps } from 'next/image'
4+
import { useEffect, useState } from 'react'
5+
6+
export default function BlurImage(props: ImageProps) {
7+
const [loading, setLoading] = useState(true)
8+
const [src, setSrc] = useState(props.src)
9+
useEffect(() => setSrc(props.src), [props.src]) // update the `src` value when the `prop.src` value changes
10+
11+
return (
12+
<Image
13+
{...props}
14+
src={src}
15+
alt={props.alt}
16+
className={`${props.className} ${loading ? 'blur-[2px]' : 'blur-0'}`}
17+
onLoadingComplete={async () => {
18+
setLoading(false)
19+
}}
20+
onError={() => {
21+
setSrc(`https://avatar.vercel.sh/${props.alt}`) // if the image fails to load, use the default avatar
22+
}}
23+
/>
24+
)
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { type EnrichedTweet } from 'react-tweet'
2+
import { nFormatter } from './utils'
3+
import { Heart, Message } from './icons'
4+
import { Tilt } from './tilt'
5+
import { TweetHeader } from './tweet-header'
6+
import { TweetText } from './tweet-text'
7+
import { TweetMedia } from './tweet-media'
8+
9+
export const DubTweet = ({
10+
tweet,
11+
noTilt,
12+
}: {
13+
tweet: EnrichedTweet
14+
noTilt?: boolean
15+
}) => {
16+
const TweetBody = (
17+
<div className="break-inside-avoid rounded-lg border border-gray-300 bg-white/20 bg-clip-padding p-6 pb-4 backdrop-blur-lg backdrop-filter">
18+
{/* User info, verified badge, twitter logo, text, etc. */}
19+
<div>
20+
<TweetHeader tweet={tweet} />
21+
{tweet.in_reply_to_status_id_str && tweet.in_reply_to_screen_name && (
22+
<div className="mt-5 text-base text-gray-500">
23+
Replying to{' '}
24+
<a
25+
className="text-[#1da1f2] no-underline"
26+
href={tweet.in_reply_to_url}
27+
target="_blank"
28+
>
29+
@{tweet.in_reply_to_screen_name}
30+
</a>
31+
</div>
32+
)}
33+
<TweetText tweet={tweet} />
34+
</div>
35+
{/* Images, Preview images, videos, polls, etc. */}
36+
<div className="-mb-2 mt-3">
37+
{tweet.mediaDetails?.length ? (
38+
<div
39+
className={
40+
tweet.mediaDetails.length === 1
41+
? ''
42+
: 'inline-grid grid-cols-2 gap-x-2 gap-y-2'
43+
}
44+
>
45+
{tweet.mediaDetails?.map((media) => (
46+
<a key={media.media_url_https} href={tweet.url} target="_blank">
47+
<TweetMedia tweet={tweet} media={media} />
48+
</a>
49+
))}
50+
</div>
51+
) : null}
52+
</div>
53+
<div className="flex justify-center space-x-8 text-sm text-gray-500 mt-5">
54+
<a
55+
className="group flex items-center space-x-3 hover:text-red-600"
56+
href={tweet.like_url}
57+
target="_blank"
58+
rel="noreferrer"
59+
>
60+
<Heart className="h-4 w-4 group-hover:fill-red-600" />
61+
<p>{nFormatter(tweet.favorite_count)}</p>
62+
</a>
63+
<a
64+
className="group flex items-center space-x-3 hover:text-blue-600"
65+
href={tweet.reply_url}
66+
target="_blank"
67+
rel="noreferrer"
68+
>
69+
<Message className="h-4 w-4 group-hover:fill-blue-600" />
70+
<p>{nFormatter(tweet.conversation_count)}</p>
71+
</a>
72+
</div>
73+
</div>
74+
)
75+
76+
return noTilt ? (
77+
TweetBody
78+
) : (
79+
<Tilt
80+
glareEnable={true}
81+
glareMaxOpacity={0.3}
82+
glareColor="#ffffff"
83+
glarePosition="all"
84+
glareBorderRadius="8px"
85+
tiltMaxAngleX={10}
86+
tiltMaxAngleY={10}
87+
>
88+
{TweetBody}
89+
</Tilt>
90+
)
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const Heart = ({ className }: { className: string }) => (
2+
<svg
3+
fill="none"
4+
shapeRendering="geometricPrecision"
5+
stroke="currentColor"
6+
strokeLinecap="round"
7+
strokeLinejoin="round"
8+
strokeWidth="1.5"
9+
viewBox="0 0 24 24"
10+
width="14"
11+
height="14"
12+
className={className}
13+
>
14+
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
15+
</svg>
16+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './heart'
2+
export * from './message'
3+
export * from './repeat'
4+
export * from './twitter'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const Message = ({ className }: { className: string }) => (
2+
<svg
3+
fill="none"
4+
shapeRendering="geometricPrecision"
5+
stroke="currentColor"
6+
strokeLinecap="round"
7+
strokeLinejoin="round"
8+
strokeWidth="1.5"
9+
viewBox="0 0 24 24"
10+
width="14"
11+
height="14"
12+
className={className}
13+
>
14+
<path d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z" />{' '}
15+
</svg>
16+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const Repeat = ({ className }: { className: string }) => (
2+
<svg
3+
fill="none"
4+
shapeRendering="geometricPrecision"
5+
stroke="currentColor"
6+
strokeLinecap="round"
7+
strokeLinejoin="round"
8+
strokeWidth="1.5"
9+
viewBox="0 0 24 24"
10+
width="14"
11+
height="14"
12+
className={className}
13+
>
14+
<path d="M17 1l4 4-4 4" />
15+
<path d="M3 11V9a4 4 0 014-4h14" />
16+
<path d="M7 23l-4-4 4-4" />
17+
<path d="M21 13v2a4 4 0 01-4 4H3" />{' '}
18+
</svg>
19+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const Twitter = ({ className }: { className?: string }) => (
2+
<svg
3+
xmlns="http://www.w3.org/2000/svg"
4+
viewBox="0 0 248 204"
5+
className={className}
6+
>
7+
<path
8+
fill="currentColor"
9+
d="M221.95 51.29c.15 2.17.15 4.34.15 6.53 0 66.73-50.8 143.69-143.69 143.69v-.04c-27.44.04-54.31-7.82-77.41-22.64 3.99.48 8 .72 12.02.73 22.74.02 44.83-7.61 62.72-21.66-21.61-.41-40.56-14.5-47.18-35.07 7.57 1.46 15.37 1.16 22.8-.87-23.56-4.76-40.51-25.46-40.51-49.5v-.64c7.02 3.91 14.88 6.08 22.92 6.32C11.58 63.31 4.74 33.79 18.14 10.71c25.64 31.55 63.47 50.73 104.08 52.76-4.07-17.54 1.49-35.92 14.61-48.25 20.34-19.12 52.33-18.14 71.45 2.19 11.31-2.23 22.15-6.38 32.07-12.26-3.77 11.69-11.66 21.62-22.2 27.93 10.01-1.18 19.79-3.86 29-7.95-6.78 10.16-15.32 19.01-25.2 26.16z"
10+
/>
11+
</svg>
12+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './tweet'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* client component wrapper around react-parallax-tilt because it uses class components
3+
* that can't be used in React Server Components.
4+
*/
5+
6+
'use client'
7+
8+
import TiltComponent, { type ReactParallaxTiltProps } from 'react-parallax-tilt'
9+
10+
export const Tilt = ({ children, ...props }: ReactParallaxTiltProps) => (
11+
<TiltComponent {...props}>{children}</TiltComponent>
12+
)

0 commit comments

Comments
 (0)