Custom Content Code Block

5 min read
Dec 21, 2024

If you’ve ever scrolled through a well-documented developer blog or API guide, you know the joy of stumbling upon beautifully styled code blocks. They’re not just a feast for the eyes but also a crucial part of any developer-facing content, ensuring clarity and engagement. In this article, we’ll build custom, sleek code blocks with single-tab and multi-tab functionality using modern web technologies. And yes, there’s a pinch of humor sprinkled along the way—because debugging CSS deserves some levity.

pymain.py
Copy code
print("Inside Custom Code Block!")

Why Bother With Custom Code Blocks?

You might wonder, "Why reinvent the wheel when tools like Prism.js or Highlight.js exist?" Well, here’s why:

  • Complete Control: Imagine being able to tweak every detail to suit your heart’s desire (or your designer’s insatiable thirst for perfection).
  • Seamless Integration: A bespoke code block blends effortlessly with your app’s design, like peanut butter in a PB&J sandwich.
  • Unique Interactions: From handy copy buttons to collapsible sections, custom blocks give you superpowers that generic libraries can’t.

Building your own also means you won’t accidentally spend an hour wondering why Prism.js refuses to highlight your .tsx file correctly (we’ve all been there).

The Building Blocks: Technologies in Play

This guide assumes you have a working knowledge of:

  • HTML, CSS, and JavaScript
  • Next.js (or React.js)
  • TailwindCSS (because writing CSS from scratch is so 2010)

Let’s dive into the key components.

Using React.js (or Next.js) Components

The CSS Magic: .codeblock-container

The CSS is where the real magic happens. With TailwindCSS’s utility-first approach, crafting a visually appealing code block is a breeze (and no, Tailwind didn’t pay me to say this).

Here’s how we make line numbers and spacing shine:

csssyntax.css
Copy code
.codeblock-container {
  pre .monokai {
    @apply !px-0 !rounded-none;
  }
  code {
    @apply  flex flex-col;
  }
  [data-highlighted-line] {
    @apply bg-blue-600/15 overflow-clip relative;
    position: relative;
  }

  [data-highlighted-line]::before {
    @apply absolute border-l-[5px] !pl-1 border-blue-700/80;
  }
  [data-rehype-pretty-code-fragment] {
    @apply !my-0 border border-background-tertiary overflow-y-hidden rounded-b-md;
  }
  [data-line] {
    @apply pl-8 -ml-1 min-h-6;
    position: relative;
    counter-increment: line-number;
  }

  [data-line]::before {
    @apply pl-2 inline-flex;
    content: counter(line-number);
    position: absolute;
    left: 0;
    color: gray;
    font-size: 0.875rem;
  }
}

Reusable Icons: CheckIcon and CopyIcon

Icons are the unsung heroes of UI. They’re tiny but mighty, and we’re making them reusable with TypeScript and Framer Motion for a touch of flair.

tsxIcons.tsx
Copy code
import { motion } from "framer-motion";

interface IconProps extends React.SVGProps<SVGSVGElement> {
  className?: string;
}

export const CheckIcon: React.FC<IconProps> = ({ className, ...rest }) => (
  <motion.svg
    fill="none"
    stroke="#f5f5f5"
    viewBox="0 0 24 24"
    height="20px"
    width="20px"
    className={`stroke-[#00ff59] ${className}`}
    {...rest}
    {...popAnimation}
  >
    <path
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth="2"
      d="M5 13l4 4L19 7"
    />
  </motion.svg>
);

export const CopyIcon: React.FC<IconProps> = ({ className, ...rest }) => (
  <svg
    fill="none"
    stroke="#f5f5f5"
    viewBox="0 0 24 24"
    height="20px"
    width="20px"
    xmlns="http://www.w3.org/2000/svg"
    className={`stroke-black/45 ${className}`}
    {...rest}
  >
    <path
      className="stroke-foreground"
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth="1.6"
      d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
    />
  </svg>
);

The Big Player: File Code Block Component

Here’s where it all comes together. This component handles the file name, icons, copy functionality, and of course, the code itself.

tsxMarkdownComponents.tsx
Copy code
"use client";

import Image from "next/image";
import React, { useState, useId } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { CheckIcon, CopyIcon } from "@/components/Icons";

interface FileNameProps extends React.HTMLAttributes<HTMLDivElement> {
  icon: string;
  fileName: string;
}

const FileName: React.FC<FileNameProps> = ({ icon, fileName, children, ...props }) => {
  const [isCopied, setIsCopied] = useState(false);
  const [isPopping, setIsPopping] = useState(false);
  const uniqueId = useId();

  const handleCopy = () => {
    const range = document.createRange();
    const element = document.getElementById(uniqueId);

    if (element) {
      range.selectNodeContents(element);
      const selection = window.getSelection();
      if (selection) {
        selection.removeAllRanges();
        selection.addRange(range);
        document.execCommand("copy");
        selection.removeAllRanges();
      }
    }

    setIsCopied(true);
    setIsPopping(true);

    setTimeout(() => {
      setIsCopied(false);
      setIsPopping(false);
    }, 2000);
  };

  return (
    <div className="my-4 relative">
      <div className="relative flex flex-col items-start space-x-2" {...props}>
        {/* code block details */}
        <div className="w-full flex bg-background-tertiary rounded-t-md overflow-hidden">
          <div className="bg-background-tertiary flex items-center justify-between w-full py-2.5 h-8">
            <div className="flex items-center px-3 gap-2">
              <span className="file-icon">
                <Image
                  src={`../packages/svg/icons/${icon}.svg`}
                  alt={icon}
                  width={18}
                  height={18}
                  className="!m-0"
                />
              </span>
              <b className="text-xs font-thin font-mono">{fileName}</b>
            </div>
          </div>
        </div>
        {/* copy button */}
        <div className="sticky top-[5.4rem] whitespace-nowrap z-10 w-full" onClick={handleCopy}>
          <div className="absolute bottom-4 right-4 flex items-center cursor-pointer gap-1 h-full bg-background-tertiary">
            <div className="py-1 pl-1.5 rounded bg-background-tertiary">
              {isCopied ? (
                  <motion.div
                  initial={{ scale: 0.6 }}
                  animate={{ scale: 1 }}
                  exit={{ scale: 0.6 }}
                  transition={{ type: "spring", stiffness: 500, damping: 30 }}
                  className="flex items-center gap-1"
                >
                  <CheckIcon className="text-primary" />
                  <span className="text-xs mr-2">Copied!</span>
                </motion.div>
              ) : (
                  <div className="flex items-center gap-1">
                  <CopyIcon className="text-foreground" />
                  <span className="text-xs mr-2">Copy code</span>
                </div>
              )}
            </div>
          </div>
        </div>
        {/* code block content */}
        <div className="w-full py-0 codeblock-container relative" id={uniqueId}>
          {children}
        </div>
      </div>
    </div>
  );
};

export default FileName;

Usage of <FileName>

mdxblog-url.mdx
Copy code
<FileName icon="py" fileName="main.py">
```py
print("Inside Custom Code Block!")
```
</FileName>

How it looks <FileName icon="mdx" fileName="blog-url.mdx">

pymain.py
Copy code
print("Inside Custom Code Block!")

Styles come and go. Good design is a language, not a style.

By now, you’ve built a custom code block that’s stylish, functional, and unapologetically yours. Not only does it fit your application like a glove, but it also comes with bragging rights: “Yes, I wrote this from scratch.” And isn’t that what being a developer is all about?

Wanna Discover more Interesting Topics?
nextjs
Dec 21, 2024

Next.js (Pages Router) and Contentlayer Setup

In this blog we will look how to setup Next.js and Contentlayer for an Blog page. Step one Install necessary deps bash npm install \ contentlayer nextcontentlayer \ shiki rehypeshiki rehypeprettycode \ rehypeautolinkheadings rehypeslug rehyperewrite rehypestringify \ remarkgfm unified \ datefns readingtime create contentlayer.config.ts file add contentlayer.config.ts in your root of the project and you have to create your blog files in this folder folder strcture of the necessary files and folders bash /public /content /blog /markdownsyntaxguide index.mdx contentlayer.config.ts tsconfig.json nextjs.config.ts ts import { defineDocumentType, ma
nextjs
Dec 21, 2024

Next.js (Pages Router) and Contentlayer Setup

Building a Scalable MDX Blog with Next.js and Contentlayer As engineering leads, one of our recurring challenges is balancing scalability, readability, and extensibility when building contentheavy frontend applications. A lightweight, composable blog system using Next.js (Pages Router) and Contentlayer checks all the right boxes—type safety, statically generated performance, and full MDX control. In this post, you'll set up a productiongrade foundation for a statically rendered blog using Contentlayer and Next.js, with an emphasis on correctness, longterm maintainability, and modern DX. Project Structure Here’s the minimal directory layout yo
powershell
May 5, 2023

Configuring PowerShell (shell prompt)

In Windows PowerShell, customizing your shell prompt can enhance your development environment by providing valuable information at a glance. This guide walks you through configuring Windows PowerShell to display a customized prompt with the username, date, and current folder name. Checking for Existing Profile STEP 1: Before proceeding, check if you have an existing profile set up in PowerShell: powershell testpath $profile STEP 2: If the result is false, create a new profile: powershell newitem path $profile itemtype file force STEP 3: Now, open the profile in Notepad: powershell notepad $profile STEP 4: Paste the following function to displ