Published on

Applying how I created a Figure element component to creating a Static Text Tweet component

Last Modified on
Last modified on
Authors
Applying how I created a Figure element component to creating a Static Text Tweet component
Photo by Camilo Jimenez on Unsplash

In a previous post entitled How to Create New Lines Inside Text Prop Values in Next.js and JavaScript in General, I discuss how I came to create a static text tweet component. It was inspired by a figure (element) component I had previously created which I use for my post images where I want to give attribution to the image and the image’s photographer.

I have to say that this Figure (element) component was inspired by the Recipe Blog called “Tambouille”, created by Github handle @aloisdg (Aloïs de Gouvello), where Alois created a Figure component to give attribution to HIS recipe post images and their photographers. I teach about the figure element and using it so as to elegantly give attribution to images and their photographers, so when I saw him do that on his Recipe Blog, it caught my attention. To check out the Tambouille repository on Github, please visit aloisdg/tambouille.

It all started with the Figure component, aka function as prop component, in which I use props destructuring, of the props I wanted to pass to the parent Interglobalmedia component, and which returns a figure element and associated figcaption element in the returned JSX of the component. This is considered the child component. I also use the Next.js Image component for image optimization, instead of the default img element there. So:

import Image from 'next/image'
import '../../styles/partials/Figure.module.scss'

const Figure = ({ alt, src, srcUrl, author, authorUrl, host, hostUrl }) => {
	if (!alt || !src || !author || !authorUrl || !host || !hostUrl) {
		return null
	}
	// eslint-disable-next-line @next/next/no-img-element
	let image = (
		<Image
			src={src}
			className="figure-image"
			alt={alt}
			width={1280}
			height={853}
			layout="intrinsic"
		/>
	)
	let caption = (
		<figcaption className="figcaption">
			Photo by{' '}
			<a href={authorUrl} title={`See the profile of ${author}`}>
				{author}
			</a>{' '}
			on{' '}
			<a href={hostUrl} title={`Open ${host}`}>
				{host}
			</a>
		</figcaption>
	)
	return (
		<figure className="figure relative flex flex-col items-center">
			{srcUrl ? (
				<a className="w-full" href={srcUrl} title="See image">
					{image}
				</a>
			) : (
				{ image }
			)}
			{caption}
		</figure>
	)
}

export default Figure

But it does not stop there. Then, I created three parent components, one called Interglobalmedia, which gives attribution to my own screenshots/images I use in my posts, one called Pexels, which gives attribution to images I use from Pexels, and one called Unsplash, which gives attribution to images I use from Unsplash. The Interglobalmedia component:

import Figure from './Figure'

const Interglobalmedia = (interglobalmediaProps) => {
	const props = {
		...interglobalmediaProps,
		host: 'Interglobalmedia',
		hostUrl: '/',
	}
	return <Figure {...props} />
}

export default Interglobalmedia

This makes for a very flexible parent component! I pass in a param called interglobalmediaProps, which I pass in and spread out with spread syntax in an object literal represented by the const variable called props. In React jargon, ...interglobalmediaProps is referred to as JSX spread attributes. There is spread syntax in vanilla JS, but JSX spread attributes, which is a JSX feature, is a syntax for passing all of an object’s properties as JSX attributes. The properties of the object I pass in are copied onto the component’s props. And then I set two key value pairs which I would (perhaps) expect in all Interglobalmedia component instances. The process is the same for the Pexels and Unsplash components.

If we don’t know all the properties which we want to set on a component, we might want to add them on to the object later. So in that case, we can use JSX spread attributes instead of explicitly defining which attributes we want pass to the component! And then, in my case in Interglobalmedia (and Pexels and Unsplash), I define only the two attributes and their values I want (perhaps) to be present in ALL my Interglobalmedia components, for example.

An example of an Interglobalmedia component I use on the site:

<Interglobalmedia
	src="/static/images/projects/img/webp/audio-visual-slider-app.webp"
	author="Maria D. Campbell"
	alt="Audio Visual Slider"
	srcUrl="/static/images/projects/img/webp/audio-visual-slider-app.webp"
	authorUrl="/about"
/>

I followed the attributes (props) I used for Pexels and Unsplash. I ignored host and hostUrl. I probably won’t ever use host and hostUrl, but I will keep those props in the parent component for now.

I had been researching how I was going to embed static tweets on here, and only came up with some very complex solutions. Packages I came across did not work, because they basically were still using JS scripts under the hood, and that meant that virtually all of the time the embedded tweet would only show up on page reload. And that makes for very bad UX.

Then I came across Maxime Heckel’s Next.js blog, where he wrote a beautiful post entitled Static Tweets with MDX and Next.js, in which he went through the steps he took to successfully create a static tweet component. But I found it to be too complex given the end goal, and he also used Typescript, which I have not yet mastered. However, the basic idea is there. Perhaps he was just a bit more “formal” about his key value pairs!

And one day, it dawned on me that all I really had to do was take the code and structure I created for my Figure element component and apply it to the creation of a static text tweet. I was only interested in tweets that essentially only contained text, so that is why StaticTextTweet. So I ended up doing the following:

import SocialIcon from '../social-icons'
import '../../styles/partials/StaticTextTweet.module.scss'

const StaticTextTweet = ({
	avatar,
	author,
	name,
	handleUrl,
	handleName,
	text,
	repliesUrl,
	replies,
	retweetsUrl,
	retweets,
	likesUrl,
	likes,
	statusUrl,
	publishDate,
	twitterDeviceUrl,
	twitterDevice,
}) => {
	return (
		<article className="static-text-tweet-article">
			<div className="static-text-tweet-div">
				<blockquote className="static-text-tweet-blockquote">
					<div className="static-text-tweet-header">
						<a
							className="static-text-tweet-avatar-link"
							href={handleUrl}
							target="_blank"
							rel="noopener noreferrer"
						>
							<img
								className="static-text-tweet-avatar"
								src={avatar}
								alt={author}
							/>
						</a>
						<a
							href={handleUrl}
							target="_blank"
							rel="noopener noreferrer"
							className="static-text-tweet-header-author"
						>
							<span className="static-text-tweet-name">
								{name}
							</span>

							<span className="static-text-tweet-handle-name text-sm">
								{handleName}
							</span>
						</a>
						<SocialIcon
							className="static-text-tweet-brand-icon"
							kind="twitter"
							href={statusUrl}
							size="5"
						/>
					</div>
					<div className="static-text-tweet-text-wrapper">
						<p className="static-text-tweet-text">{text}</p>
					</div>
					<div className="static-text-tweet-info">
						<a
							href={statusUrl}
							target="_blank"
							rel="noopener noreferrer"
						>
							<b className="static-text-tweet-publish-date">
								{publishDate}
							</b>
						</a>
						<a
							href={twitterDeviceUrl}
							target="_blank"
							rel="noopener noreferrer"
						>
							<b className="static-text-tweet-device">
								{twitterDevice}
							</b>
						</a>
					</div>
					<div className="static-text-tweet-intents">
						<a
							href={repliesUrl}
							target="_blank"
							rel="noopener noreferrer"
							className="static-text-tweet-replies-icon"
						>
							<svg
								width="24"
								height="24"
								viewBox="0 0 24 24"
								fill="currentColor"
							>
								<path d="M14.046 2.242l-4.148-.01h-.002c-4.374 0-7.8 3.427-7.8 7.802 0 4.098 3.186 7.206 7.465 7.37v3.828c0 .108.045.286.12.403.143.225.385.347.633.347.138 0 .277-.038.402-.118.264-.168 6.473-4.14 8.088-5.506 1.902-1.61 3.04-3.97 3.043-6.312v-.017c-.006-4.368-3.43-7.788-7.8-7.79zm3.787 12.972c-1.134.96-4.862 3.405-6.772 4.643V16.67c0-.414-.334-.75-.75-.75h-.395c-3.66 0-6.318-2.476-6.318-5.886 0-3.534 2.768-6.302 6.3-6.302l4.147.01h.002c3.532 0 6.3 2.766 6.302 6.296-.003 1.91-.942 3.844-2.514 5.176z"></path>
							</svg>
							<b className="static-text-tweet-replies">
								{replies}
							</b>
						</a>
						<a
							href={retweetsUrl}
							target="_blank"
							rel="noopener noreferrer"
							className="static-text-tweet-retweets-icon"
						>
							<svg
								width="24"
								height="24"
								viewBox="0 0 24 24"
								fill="currentColor"
							>
								<path d="M23.77 15.67c-.292-.293-.767-.293-1.06 0l-2.22 2.22V7.65c0-2.068-1.683-3.75-3.75-3.75h-5.85c-.414 0-.75.336-.75.75s.336.75.75.75h5.85c1.24 0 2.25 1.01 2.25 2.25v10.24l-2.22-2.22c-.293-.293-.768-.293-1.06 0s-.294.768 0 1.06l3.5 3.5c.145.147.337.22.53.22s.383-.072.53-.22l3.5-3.5c.294-.292.294-.767 0-1.06zm-10.66 3.28H7.26c-1.24 0-2.25-1.01-2.25-2.25V6.46l2.22 2.22c.148.147.34.22.532.22s.384-.073.53-.22c.293-.293.293-.768 0-1.06l-3.5-3.5c-.293-.294-.768-.294-1.06 0l-3.5 3.5c-.294.292-.294.767 0 1.06s.767.293 1.06 0l2.22-2.22V16.7c0 2.068 1.683 3.75 3.75 3.75h5.85c.414 0 .75-.336.75-.75s-.337-.75-.75-.75z"></path>
							</svg>
							<b className="static-text-tweet-retweets">
								{retweets}
							</b>
						</a>
						<a
							className="static-text-tweet-likes-icon"
							href={likesUrl}
							target="_blank"
							rel="noopener noreferrer"
						>
							<svg
								width="24"
								height="24"
								viewBox="0 0 24 24"
								fill="currentColor"
							>
								<path d="M12 21.638h-.014C9.403 21.59 1.95 14.856 1.95 8.478c0-3.064 2.525-5.754 5.403-5.754 2.29 0 3.83 1.58 4.646 2.73.813-1.148 2.353-2.73 4.644-2.73 2.88 0 5.404 2.69 5.404 5.755 0 6.375-7.454 13.11-10.037 13.156H12zM7.354 4.225c-2.08 0-3.903 1.988-3.903 4.255 0 5.74 7.035 11.596 8.55 11.658 1.52-.062 8.55-5.917 8.55-11.658 0-2.267-1.822-4.255-3.902-4.255-2.528 0-3.94 2.936-3.952 2.965-.23.562-1.156.562-1.387 0-.015-.03-1.426-2.965-3.955-2.965z"></path>
							</svg>{' '}
							<b className="static-text-tweet-likes">{likes}</b>
						</a>
					</div>
				</blockquote>
			</div>
		</article>
	)
}

export default StaticTextTweet

Basically, this set the properties I was going to use via destructuring in the parent component. And then I created a parent component called TextTweetEmbed, and it looked like the following:

import StaticTextTweet from './StaticTextTweet'

const TextTweetEmbed = (textTweetEmbedProps) => {
	const props = {
		...textTweetEmbedProps,
	}
	return <StaticTextTweet {...props} />
}

export default TextTweetEmbed

So all the hard work was in finding the appropriate data for the prop values, and in the styling. My styling for the static tweet was the following:

/* color variables */
$white: #fff;
$twitter-blue: #3b94d9;

.static-text-tweet-article {
	background: $white;
	border-radius: 0.5rem;
	border: 1px solid lighten(#ccc, 5%);
	display: block;
	margin: 0 auto;
}

.static-text-tweet-div {
	overflow: hidden;
}

.static-text-tweet-blockquote {
	font-style: normal;
	padding: 0.625rem 1.25rem 0.625rem 1.25rem;
	position: relative;

	& a.text-gray-500 {
		margin-left: auto;
	}
}

.static-text-tweet-header {
	display: flex;

	& a.text-gray-500 svg {
		fill: lighten($twitter-blue, 5%);
	}
}

.static-text-tweet-header-author {
	display: flex;
	flex-direction: column;
	margin-top: -0.5rem;
	text-decoration: none;

	& .static-text-tweet-name {
		color: #000;
		font-weight: bold;
	}

	& span:nth-of-type(2) {
		color: darken(#aaa, 15%);
	}
}

.static-text-tweet-avatar {
	border-radius: 50%;
	width: 36px;
	height: 36px;
	margin-top: 0;
	margin-right: 0.75rem;
}

.static-text-tweet-text {
	/* Will break line on \n and wrap text according to parent width. */
	white-space: pre-wrap;
}

.static-text-tweet-text::before,
.static-text-tweet-text::after {
	content: '';
}

.static-text-tweet-info > a:first-of-type {
	margin-right: 1rem;
}

.static-text-tweet-info > a {
	text-decoration: none;
}

.static-text-tweet-publish-date,
.static-text-tweet-device {
	font-weight: normal;
}

.static-text-tweet-intents {
	display: flex;
	justify-content: space-between;
	margin-top: 1.5rem;
}

.static-text-tweet-intents > a {
	text-decoration: none;

	& b {
		font-weight: normal;
	}
}

.static-text-tweet-replies-icon,
.static-text-tweet-retweets-icon,
.static-text-tweet-likes-icon {
	display: flex;
}

So it should not always be about showing off how much we know in our code, but achieving the same goal with the most readable and effective code that perhaps is even simpler to create. And that is what I did here.

Happy tweeting!