A Grid Of Ones Own

In the ever-evolving landscape of web development, the integration of personalized components adds a unique touch to one's portfolio. Over the past several years, I have had the chance to put in many, many hours on various professional and personal projects, but how can I show others that I'm putting in the time? Now that I find myself in the market for new work, I wanted to find a way to share that dedication with the world in a centralized location. The solution for me came in the form of a Git commit grid—a visual representation of my commit history from GitHub and GitLab APIs neatly displayed on my portfolio website. Keep in mind this only represents the work that I have done outside of my 9-5 over the past year, but I believe what ends up coming through is a clear dedication (obsession? 😅). Let's dive into the details a bit!

A 52 column, 7 row grid with cells variously filled with either dark gray or bright teal.

Let's start by talking about the render logic for the component. The component renders a grid of cells, with each cell representing a single day. The styles are conditionally applied, representing days that saw commits, days that didn't, and days that shouldn't end up in the grid at all (future days for instance). As the component renders, it calculates the position of the cell, the date it represents, and the number of commits for that date. All of that data is accessible in hash tables that have keys corresponding to the dates each cell represents. As the component renders, it can just lookup the location in the corresponding table and render accordingly. The grid is topped with the months associated with the cells beneath and a nav that allow users to look at commits by service. Let's have a look at the code:

To determine the location that corresponds to the day of the year(s) for each cell, we use the grid row and column to calculate our number:

let location = ((
  ((colIndex * 7) + (rowIndex))
  + (firstMonth.daysIntoYear - firstMonth.firstDay)) + 7)
  % daysInGivenYear
if (location < 1) location = (daysInGivenYear + location) - 1
  • First, we organize everything into weeks with (colIndex * 7) + rowIndex.
  • Then we subtract the day of the week of the first day of the first month from the day-of-the-year of the first day of the first month that will be displayed (which is the farthest month back, since the current month will be displayed all the way to the right) (whew, that's a mouthful).
  • The day-of-the-year is just a counter of days throughout the year, with January 1st being day 1, and December 31st being 365 (or 366 on leap years).
  • Finally, seven is added to shift the grid over one week (in case the previous calculation gave us a negative number).
  • The number we end up with is the remainder when dividing by the number of days in the given year, and we are left with the location.
  • If the result is negative, we add that (negative) number of days to the number of days in the given year and subtract one to get our actual location.

It is a little tricky to understand all the logic behind this, but once you see it in action it starts to make sense.

The next bit of code I want to highlight is the method that is responsible for hashing commit data:

const calculateContributions = useCallback((data: Contribution[]) => {
	const contributions: { [key: number]: Contribution[] | undefined } = {}
	data.forEach((entry) => {
		const dayOfYear = daysIntoYear(entry.date)
		if (contributions[dayOfYear]) contributions[dayOfYear]?.push(entry)
		else contributions[dayOfYear] = [entry]
	})
	return contributions
}, [daysIntoYear])

This method takes in an array of already normalized contributions that have come back from the API, uses the hash function that is responsible for generating a key that represents the date's location in the grid. If there is already an entry in said location, the commit data is added to the array there. If not, a new array is created. This way, there is no iteration necessary when it comes time to render, and there is simply a lookup that needs to happen, significantly reducing runtime. The array's length then also becomes our commit count. Additionally, the function is wrapped in a useCallback hook, allowing the function to be stored, and the result is wrapped in a useMemo hook, so the returned calculation is not unnecessarily repeated.

There is one final element that proved to be an interesting little challenge while creating this component. The component was built with responsiveness in mind, allowing mobile users to enjoy the commit grid as well as desktop users could. However, when the component renders on a small screen, the natural behavior was for the user to have to scroll to the right. In other words, the oldest commits were visible by default, and only by scrolling could you see the newest commits. Intuitively, this seemed wrong to me. I wanted the "present" to be in the window, making the user scroll "back" in time to see older commits.

A screenshot of the commit grid component on a mobile screen, with the right-most portion of the component visible in the view window.

In order to accomplish this, I took advantage of the dir attribute. The dir attribute is responsible for identifying the direction of the text within a given element, accounting for languages that are read from right to left, such as Arabic, Hebrew, or Urdu. However in this case, I am using it to indicate to the browser that the right-hand side of the component is the part that should be in view by default. Since there is text within the component, an additional dir of rtl must be set on the inner component, so that the text renders correctly. Here is the code:

{/* direction set right-to-left for the component */}
<div dir="rtl" className="flex justify-center">
	<div className="grid gap-[3px] overflow-x-scroll overflow-y-hidden">
		{/* direction set left-to-right for the header row */}
		<span dir="ltr" className="grid gap-[3px]">
			{/* Month Header Row */}
			<div className="grid-cols-12 w-[962px] grid gap-[3px]">
				{months.map((month, index) => (
					<div key={index} className="p-2 text-center text-zinc-600 dark:text-zinc-400 font-medium text-xs">
						{month.abbr}
					</div>
				))}
			</div>
            {/* ... Grid Logic ... */}
		</span>
	</div>
</div>

This Git commit grid is not merely a visual display—I hope that it encapsulates a narrative of dedication and craftsmanship, not only of the component itself, but of the projects I have poured over for at least the past year. I had an absolute blast working on this component, and I can't stop thinking about enhancements I'd like to make.

For a deeper dive into the code, this repository is public on my GitHub profile. Feel free to take a look and let me know what you think! Is there anything you would add? What would you do differently? I'd love to hear about it!

Commits
AllGitHubGitLab

Oct
Nov
Dec
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep