How to style highlightedDates buttons dynamically in IonDatetime

Goal

Using Ionic/React 7.0.0 - React 18.2.0

In my IonDatetime component, I want to style calendar day buttons based on

  • Input data : (1) available session during a day → Display a green calendar day (2) busy day → red.
  • User interaction : available or busy date is clicked, I’d like to pick the color of my choice and prevent base color to apply.

What I tried

  • When a calendar day is clicked, I’m setting the date in a state.
  • I have a useEffect (with the given state as a dependancy) which is setting the relevant array for my highlightedDatesprops in IonDatetime.
See code

/** style.css **/
.booking-calendar {
    --background: unset;
}

/** AdminBooking.tsx **/
interface HighlightedDate {
  date: string;
  textColor: string;
  backgroundColor?: string;
}

const AdminBooking: React.FC<AdminBookingProps> = ({}) => {
  const [activeDate, setActiveDate] = useState<string | null>(null);
  const [highlightedDates, setHighlightedDates] = useState<HighlightedDate[]>();
  const dates = [
    {
      startDate: "2024-08-02",
      sessions : [
        {
          sessionId: 1,
          status: "opened",
          name: "Séance route",
          program: "Virage, maîtrise frein avant",
          supervisor: {
            firstName: "Dan"
          }
        }
      ],
    },
    {
      startDate: "2024-08-03",
      sessions : [
        {
          sessionId: 2,
          status: "full",
          name: "Séance route",
          program: "Virage, maîtrise frein avant",
          supervisor: {
            firstName: "Dan"
          }
        },
        {
          sessionId: 2,
          status: "full",
          name: "Séance route",
          program: "Virage, maîtrise frein avant",
          supervisor: {
            firstName: "Dan"
          }
        }
      ],
    },
    {
      startDate: "2024-08-04",
      sessions : [
        {
          sessionId: 3,
          status: "full",
          name: "Séance route",
          program: "Virage, maîtrise frein avant",
          supervisor: {
            firstName: "Dan"
          }
        },
        {
          sessionId: 4,
          status: "opened",
          name: "Séance route",
          program: "Virage, maîtrise frein avant",
          supervisor: {
            firstName: "Dan"
          }
        }
      ],
    },
  ]
  function handleDateClick(e: any) {
    setActiveDate(e.detail.value);
  }
  useEffect(() => {
    const styledDates = dates.map((date) => {
      const hasOpenedSession = date.sessions.find((session) => session.status === "opened");
      const hasFocus = date.startDate === activeDate?.slice(0, 10);
      if (hasFocus) {
        return {
          date: date.startDate,
          textColor:'#fff',
          backgroundColor: hasOpenedSession ? '#457d78' : '#c3645f',
        }
      } else {    
        return {
          date: date.startDate,
          textColor: hasOpenedSession ? '#457d78' : '#c3645f',
          backgroundColor: '#fff',
        }
      }
    })
    console.log({styledDates})
    setHighlightedDates(styledDates)
  }, [activeDate])

  return (
    <>
      <h2>Calendrier</h2>
      <Spacer units={10} />
      <IonDatetime
        className="booking-calendar"
        firstDayOfWeek={1}
        id="datetime"
        presentation="date"
        min={today().toISOString().slice(0, 10)}
        highlightedDates={highlightedDates}
        onIonChange={(e) => handleDateClick(e)}
      ></IonDatetime>
    </>  
  );
};

Logs, Screenshots

See console.log output and screen shot

Logs, First log is when August 2nd is clicked, second is when August 3rd is clicked

// First
{
    "styledDates": [
        {
            "date": "2024-08-02",
            "textColor": "#fff",
            "backgroundColor": "#457d78"
        },
        {
            "date": "2024-08-03",
            "textColor": "#c3645f"
        },
        {
            "date": "2024-08-04",
            "textColor": "#457d78"
        }
    ]
}
// Second
{
    "styledDates": [
        {
            "date": "2024-08-02",
            "textColor": "#457d78"
        },
        {
            "date": "2024-08-03",
            "textColor": "#fff",
            "backgroundColor": "#c3645f"
        },
        {
            "date": "2024-08-04",
            "textColor": "#457d78"
        }
    ]
}

Screenshot 1

Screenshot 2, August 2nd in screenshot 2 (same thing happens for the 3rd but in red)

Screenshot 3, August 4th in screenshot 2

Problem

The styles applies properly when calendar day buttons are just displayed but they don’t apply as expected when the calendar day is active (clicked)

  • Programmatically the logic works well, as you can see in the logs (above)
  • Using background or backgroundColor results the same
  • If I comment backgroundColor: '#fff', calendar day button has a colored background and font from both section of the if hasFocus applies (see screenshot 1, 2, 3)
  • If I keep backgroundColor: '#fff', the colored background and font doesn’t apply, just the active style that I do not want (screenshot 0)

Questions

  • Am I approaching the issue the wrong way ?
  • Does anyone have a clue of how to change style for highlightedDates based on element state ?

It seems like using calendar-day active part would work for you. See ion-datetime: Ionic API Input for Datetime Format Picker.

Example:

<style>
  ion-datetime::part(calendar-day active),
  ion-datetime::part(calendar-day active):focus {
      background-color: #9ad162;
      border-color: #9ad162;
      color: #fff;
    }
</style>

Thanks for your answer.
This won’t work for me because I want the focus colors in the shadow dom to be be based on some conditions as explained in the Goal section.

In other words

  • If a day is full: font color of the part(calendar-day active) should be red. If that day is clicked, font color should be white and background red
  • If a day is opened: font color should be green. If that day is clicked, font color should be white and background green

I was unfamiliar with css variables but I’ve found the following workaround:

  • Using CSS Variables to apply some style
  • Dynamically change the style with JS
New Code

In variables.css

:root {
  /* at the end */
  --active-date-bg-color: #9ad162;
  --active-date-box-shadow: unset;
  --active-date-text-color: #fff;
}

In style.css

ion-datetime::part(calendar-day active),
ion-datetime::part(calendar-day active):focus,
ion-datetime::part(calendar-day):focus {
  background-color: var(--active-date-bg-color);
  box-shadow: var(--active-date-box-shadow);
  color: var(--active-date-text-color);
}

In my AdminBooking component

const AdminBooking: React.FC<AdminBookingProps> = ({}) => {
  const [activeDate, setActiveDate] = useState<string | null>(null);
  const dates = [
    {
      startDate: "2024-08-02",
      sessions : [
        {
          id: 1,
          status: "opened",
          name: "Séance route",
          program: "Virage, maîtrise frein avant",
          supervisor: {
            firstName: "Dan"
          }
        }
      ],
    },
    {
      startDate: "2024-08-03",
      sessions : [
        {
          id: 2,
          status: "full",
          name: "Séance route",
          program: "Virage, maîtrise frein avant",
          supervisor: {
            firstName: "Dan"
          }
        },
        {
          id: 2,
          status: "full",
          name: "Séance route",
          program: "Virage, maîtrise frein avant",
          supervisor: {
            firstName: "Dan"
          }
        }
      ],
    },
    {
      startDate: "2024-08-04",
      sessions : [
        {
          id: 3,
          status: "full",
          name: "Séance route",
          program: "Virage, maîtrise frein avant",
          supervisor: {
            firstName: "Dan"
          }
        },
        {
          id: 4,
          status: "opened",
          name: "Séance route",
          program: "Virage, maîtrise frein avant",
          supervisor: {
            firstName: "Dan"
          }
        }
      ],
    },
  ]

  useEffect(() => {
    const hasSession = dates.find((date) => date.startDate === activeDate);
    if (!hasSession) {
      document.documentElement.style.setProperty('--active-date-bg-color', '#9ccec4');
    } else {
      dates.map((date) => {
        const hasSessionAvailable = date.sessions.find((session) => session.status === "opened");
        if (date.startDate === activeDate && hasSessionAvailable){
          document.documentElement.style.setProperty('--active-date-bg-color', '#457d78');
        } else if (date.startDate === activeDate && !hasSessionAvailable) {
          document.documentElement.style.setProperty('--active-date-bg-color', '#c3645f');
        }
      })
    }
  }, [activeDate])

  function handleDateClick(e: any) {
    e.preventDefault();
    setActiveDate(e.detail.value.slice(0, 10));
  }
  const highlightedDates = dates.map((date) => {
    const hasOpenedSession = date.sessions.find((session) => session.status === "opened");
    return {
      date: date.startDate,
      textColor: hasOpenedSession ? '#457d78' : '#c3645f',
    }
  })

  return (
    <>
      <h2>Calendrier</h2>
      <Spacer units={10} />
      <IonDatetime
        className="booking-calendar"
        firstDayOfWeek={1}
        id="datetime"
        presentation="date"
        min={today().toISOString().slice(0, 10)}
        highlightedDates={highlightedDates}
        onIonChange={(e) => handleDateClick(e)}
      ></IonDatetime>
    </>

  );
};

export default AdminBooking;

Expected behavior :white_check_mark:

Aug-01-2024 13-23-02

1 Like