Pro Tailwind

00. Challenge starting point

🐇 TL;DR

  • This is real-life code I ended up with while prototyping a Calendar component
  • There is unnecessary complexity in the Calendar Day styling
  • Let’s take a look at where we’re at 👀

⬇️ Skip to the challenge


🐢 Background

While prototyping the Calendar App, I spent a few days messing around with the Calendar component.

The CalendarDay component particulary suffered a bit of “complexity creep” over time, as I was discovering new “edge cases” and adding some conditional logic to handle styling.

I had good intentions. I started with a style lookup object, just like we’ve done in the previous two challenges:

const baseClasses = 'relative w-12 max-w-full ...'

const dynamicClasses = {
  disabled: 'text-slate-300 pointer-events-none',
  candidate: 'hover:bg-slate-100 text-slate-900',
  hasAvailability: 'bg-indigo-100 text-indigo-600 font-bold hover:bg-indigo-200',
  today: 'text-indigo-600 font-bold hover:bg-slate-100',
  selected: 'bg-indigo-600 text-white font-bold bg-stripes',
}

The keys in the dynamicClasses object are what I determined would be the different statuses a calendar day could be in:

  • disabled: the day is not available, because it’s in the past or in a future month
  • candidate: the day is within the available range, but there is no booking availability on that day
  • hasAvailability: the day has some booking availabilies
  • today: the day is today!
  • selected: the day is currently selected

It’s a bit more complicated than that

While this list of statuses really helped me provide styles for each scenario, I progressively realised that there was a bit more to it, in terms of complexity.

For example, a calendar day could actually be in both today and hasAvailability statuses at the same time.

In that case, which styles should be applied?

I ended up having a progressively more and more confusing series of conditional checks to handle style precedence between each status.

And here’s what my code ended up looking like:

<CalendarDay
  {...props}
  className={cx(
    baseClasses,
    isSelected && dynamicClasses.selected,
    isDisabled && dynamicClasses.disabled,
    isCurrentDay && !isSelected && dynamicClasses.today,
    // Oh nooo... this is getting out of control!
    hasAvailability && !isDisabled && !isSelected && dynamicClasses.hasAvailability,
    !hasAvailability && !isCurrentDay && !isDisabled && !isSelected && dynamicClasses.candidate
  )}
/>

I tried to revrite the conditional logic with nested ternary operators, but trust me: it didn’t make it easier to reason about the logic.

You can see the starting point code in GitPod if you want.


This is not just me

Let’s take a look at another Calendar component example out there, and check how they handle dynamic styles for the individual days.

Here’s one of the Calendar components from Tailwind UI:

// Sample from Tailwind UI
<button
  className={cx(
    'py-1.5 hover:bg-gray-100',
    day.isCurrentMonth ? 'bg-white' : 'bg-gray-50',
    (day.isSelected || day.isToday) && 'font-semibold',
    day.isSelected && 'text-white',
    !day.isSelected && day.isCurrentMonth && !day.isToday && 'text-gray-900',
    !day.isSelected && !day.isCurrentMonth && !day.isToday && 'text-gray-400',
    day.isToday && !day.isSelected && 'text-indigo-600',
    // ...
  )}
>

Hmmmmm. I see a similar pattern where each overlapping scenarios are handled one by one.

And there are a few other examples out there, which try to handle conditional logic inside the className attribute.

There is nothing fundamentally wrong with handing all the conditions within the className attribute, but I think there can be better ways to do it, at least in the context of our Calendar Day component.

And that’s what we’re going to explore in this last workshop challenge!


🏆 Ready for some refactoring? 🏆

Of course you are!

Let’s get started!