Skip to content

Catalina-Hasnas/Pomodoro-App

Repository files navigation

Frontend Mentor - Pomodoro app solution

This is a solution to the Pomodoro app challenge on Frontend Mentor, using React with Recoil.

Table of contents

Overview

The challenge

Users should be able to:

  • Set a pomodoro timer and short & long break timers
  • Customize how long each timer runs for
  • See a circular progress bar that updates every minute and represents how far through their timer they are
  • Customize the appearance of the app with the ability to set preferences for colors and fonts
  • Be sent a browser notification and a beeping sound when the timer finishes

Screenshot

Desktop-app Mobile-app Settings

Links

My process

Built with

  • React
  • Recoil
  • SASS
  • Container queries and CSS clamp

What I learned

1. Usage of container queries and clamp;

Making the settings dialog into a container and having media queries based on the container size instead of the screen size proved to be efficient. So is using clamp to have the dialog width adjust based on the viewport width with a min and a max width according to the figma designs.

dialog {
  container-type: inline-size;
  container-name: dialog;
  width: clamp(20.4375rem, 40vw, 33.75rem);
  ...;
}

@container dialog (min-width: 30rem) {
  .numeric-inputs-container {
    flex-direction: row;
    justify-content: space-between;
    gap: 0.3rem;
  }
}

2. Usage of Recoil for state management

I enjoyed learning Recoil for this challenge, which I found to be intuitive. One feature I found particularly useful is the selector method, which permorms operations on the state and returns a new value from the said state. In my case, I used it to get the number of seconds of the active tab, without the need to know what the active tab even is.

export const activeTabSecondsSelector = selector({
  key: "activeTabSeconds",
  get: ({ get }) => {
    const activeTab = get(activeTabAtom);
    const pomodoroSeconds = get(pomodoroSecondsAtom);
    const shortBreakSeconds = get(shortBreakSecondsAtom);
    const longBreakSeconds = get(longBreakSecondsAtom);

    const tabsNameSeconds = {
      pomodoro: pomodoroSeconds,
      "short break": shortBreakSeconds,
      "long break": longBreakSeconds,
    };

    return tabsNameSeconds[activeTab];
  },
});
  1. Animating the stroke-dashoffset CSS property on svg circle.

Changed the value of stroke-dashoffset depending on the percent we get when calculating the time that has passed over how much time it is in total. Then, it can be easily animated.

    const strokeDashOffsetBar = ((100 - percent) / 100) * dashArr;

    <circle
        id="bar"
        r={radius}
        cx={xy}
        cy={xy}
        fill="transparent"
        strokeDasharray={dashArr}
        strokeDashoffset="0"
        style={{ strokeDashoffset: `-${strokeDashOffsetBar}px` }}
    >
#svg #circle {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1s linear;
  stroke: var(--accent-color);
  stroke-width: 0.2rem;
  stroke-linecap: round;
}
  1. Requesting permission and sending browser and audio notifications on timer end.

Initially I made the request to send notifications on page load, but I found out that this is an issue when running the app through Lighthouse. It makes sense, you wouldn't want to be promting to accept notifications from a site you've just opened the first time. So I moved the code to request permission on click on "START" button when you first start a timer. Seems like a much better user experience. I set the audio to play and the notification to be sent in my function that I know will execute each second and I have the most recent version of the seconds state.

const handleClick = () => {
    ...
    Notification.requestPermission();
    ...
    const newIntervalId = setInterval(() => {
      setSeconds((prevSeconds) => {
        if (prevSeconds === 1) {
          audio.play();
          new Notification("Time's up!");
        }
        return prevSeconds > 0 ? prevSeconds - 1 : 0;
      });
    }, 1000);
}
  1. Exporting the Timer Component to a Library and using that
import { Timer } from "@catalinahasnas/react-timer-component";

<Timer
  accentColor={accentColorCode[accentColor]}
  seconds={activeTabSeconds}
  timerStyles={{
    background: "linear-gradient(315deg, #2e325a 0%, #0e112a 100%)",
    "box-shadow": "50px 50px 100px 0px #121530, -50px -50px 100px 0px #272c5a", margin: "auto",
  }}
  width="clamp(18.75rem, 50vw, 25.625rem)"
  sendBrowserNotification
/>

Continued development

Future plans are to improve the Timer Component and export it as a small library.

UPDATE: Already did that! You can find the React Timer Component Library here

Useful resources

Author