feat(bookings): conditionally show heading/subtitle based on view (#25889)

* feat(bookings): conditionally show heading/subtitle based on view

- Add heading and subtitle props back to ShellMainAppDir in page.tsx
- Use headerClassName marker to identify the heading element
- Add useLayoutEffect in BookingCalendarContainer to hide heading when calendar view is mounted
- Restore heading visibility when switching back to list view

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* refactor(bookings): extract shell heading visibility into reusable hook

- Create useBookingsShellHeadingVisibility hook with explicit visible parameter
- Move visibility logic from BookingCalendarContainer to BookingsContent
- Use data attribute to track if we hid the header (idempotent/Strict-Mode-safe)
- Explicit show/hide based on view state instead of relying on unmount

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* refactor(bookings): simplify shell heading visibility hook

Remove HIDDEN_DATA_ATTR tracking since the header wrapper won't have
a hidden class from any other source

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Eunjae Lee
2025-12-17 12:46:12 +00:00
committed by GitHub
co-authored by Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 970af8bd6a
commit e0a28af46e
3 changed files with 34 additions and 2 deletions
@@ -1,6 +1,6 @@
import { ShellMainAppDir } from "app/(use-page-wrapper)/(main-nav)/ShellMainAppDir";
import type { PageProps } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { _generateMetadata, getTranslate } from "app/_utils";
import { cookies, headers } from "next/headers";
import { redirect } from "next/navigation";
import { z } from "zod";
@@ -34,6 +34,7 @@ const Page = async ({ params }: PageProps) => {
if (!parsed.success) {
redirect("/bookings/upcoming");
}
const t = await getTranslate();
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
let canReadOthersBookings = false;
@@ -59,7 +60,10 @@ const Page = async ({ params }: PageProps) => {
: false;
return (
<ShellMainAppDir>
<ShellMainAppDir
heading={t("bookings")}
subtitle={t("bookings_description")}
headerClassName="bookings-shell-heading">
<BookingsList
status={parsed.data.status}
userId={session?.user?.id}
@@ -0,0 +1,25 @@
"use client";
import { useLayoutEffect } from "react";
const MARKER_CLASS = "bookings-shell-heading";
export function useBookingsShellHeadingVisibility({ visible }: { visible: boolean }) {
useLayoutEffect(() => {
const marker = document.querySelector(`.${MARKER_CLASS}`);
if (!marker) return;
const headerWrapper = marker.closest("header")?.parentElement;
if (!headerWrapper) return;
if (visible) {
headerWrapper.classList.remove("hidden");
} else {
headerWrapper.classList.add("hidden");
}
return () => {
headerWrapper.classList.remove("hidden");
};
}, [visible]);
}
@@ -10,6 +10,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@calcom/ui/classNames";
import { BookingListContainer } from "../components/BookingListContainer";
import { useBookingsShellHeadingVisibility } from "../hooks/useBookingsShellHeadingVisibility";
import { useBookingsView } from "../hooks/useBookingsView";
import type { validStatuses } from "../lib/validStatuses";
@@ -70,6 +71,8 @@ export default function Bookings(props: BookingsProps) {
function BookingsContent({ status, permissions, bookingsV3Enabled }: BookingsProps) {
const [view] = useBookingsView({ bookingsV3Enabled });
useBookingsShellHeadingVisibility({ visible: view === "list" });
return (
<div className={classNames(view === "calendar" && "-mb-8")}>
{view === "list" && (