@@ -14,6 +14,7 @@ import type { PeriodData } from "@calcom/types/Event";
1414import classNames from "@calcom/ui/classNames" ;
1515import { Button } from "@calcom/ui/components/button" ;
1616import { SkeletonText } from "@calcom/ui/components/skeleton" ;
17+ import { Tooltip } from "@calcom/ui/components/tooltip" ;
1718
1819import NoAvailabilityDialog from "./NoAvailabilityDialog" ;
1920
@@ -56,6 +57,8 @@ export type DatePickerProps = {
5657 } [ ]
5758 > ;
5859 periodData ?: PeriodData ;
60+ // Whether this is a compact sidebar view or main monthly view
61+ isCompact ?: boolean ;
5962} ;
6063
6164const Day = ( {
@@ -65,6 +68,8 @@ const Day = ({
6568 away,
6669 emoji,
6770 customClassName,
71+ showMonthTooltip,
72+ isFirstDayOfNextMonth,
6873 ...props
6974} : JSX . IntrinsicElements [ "button" ] & {
7075 active : boolean ;
@@ -75,12 +80,14 @@ const Day = ({
7580 dayContainer ?: string ;
7681 dayActive ?: string ;
7782 } ;
83+ showMonthTooltip ?: boolean ;
84+ isFirstDayOfNextMonth ?: boolean ;
7885} ) => {
7986 const { t } = useLocale ( ) ;
8087 const enabledDateButtonEmbedStyles = useEmbedStyles ( "enabledDateButton" ) ;
8188 const disabledDateButtonEmbedStyles = useEmbedStyles ( "disabledDateButton" ) ;
8289
83- return (
90+ const buttonContent = (
8491 < button
8592 type = "button"
8693 style = { disabled ? { ...disabledDateButtonEmbedStyles } : { ...enabledDateButtonEmbedStyles } }
@@ -113,6 +120,33 @@ const Day = ({
113120 ) }
114121 </ button >
115122 ) ;
123+
124+ const content = showMonthTooltip ? (
125+ < Tooltip content = { date . format ( "MMMM" ) } > { buttonContent } </ Tooltip >
126+ ) : (
127+ buttonContent
128+ ) ;
129+
130+ return (
131+ < >
132+ { isFirstDayOfNextMonth && (
133+ < div
134+ className = { classNames (
135+ "absolute top-0 z-10 mx-auto w-fit rounded-full font-semibold uppercase tracking-wide" ,
136+ active ? "text-white" : "text-default" ,
137+ disabled && "bg-emphasis"
138+ ) }
139+ style = { {
140+ fontSize : "10px" ,
141+ lineHeight : "13px" ,
142+ padding : disabled ? "0 3px" : "3px 3px 3px 4px" ,
143+ } } >
144+ { date . format ( "MMM" ) }
145+ </ div >
146+ ) }
147+ { content }
148+ </ >
149+ ) ;
116150} ;
117151
118152const Days = ( {
@@ -129,6 +163,7 @@ const Days = ({
129163 customClassName,
130164 isBookingInPast,
131165 periodData,
166+ isCompact,
132167 ...props
133168} : Omit < DatePickerProps , "locale" | "className" | "weekStart" > & {
134169 DayComponent ?: React . FC < React . ComponentProps < typeof Day > > ;
@@ -143,20 +178,48 @@ const Days = ({
143178 scrollToTimeSlots ?: ( ) => void ;
144179 isBookingInPast : boolean ;
145180 periodData : PeriodData ;
181+ isCompact ?: boolean ;
146182} ) => {
147- // Create placeholder elements for empty days in first week
148- const weekdayOfFirst = browsingDate . date ( 1 ) . day ( ) ;
149-
150183 const includedDates = getAvailableDatesInMonth ( {
151184 browsingDate : browsingDate . toDate ( ) ,
152185 minDate,
153186 includedDates : props . includedDates ,
154187 } ) ;
155188
156- const days : ( Dayjs | null ) [ ] = Array ( ( weekdayOfFirst - weekStart + 7 ) % 7 ) . fill ( null ) ;
157- for ( let day = 1 , dayCount = daysInMonth ( browsingDate ) ; day <= dayCount ; day ++ ) {
158- const date = browsingDate . set ( "date" , day ) ;
159- days . push ( date ) ;
189+ const today = dayjs ( ) ;
190+ const firstDayOfMonth = browsingDate . startOf ( "month" ) ;
191+ const isSecondWeekOver = today . isAfter ( firstDayOfMonth . add ( 2 , "week" ) ) ;
192+ let days : ( Dayjs | null ) [ ] = [ ] ;
193+
194+ const getPadding = ( day : number ) => ( browsingDate . set ( "date" , day ) . day ( ) - weekStart + 7 ) % 7 ;
195+ const totalDays = daysInMonth ( browsingDate ) ;
196+
197+ // Only apply end-of-month logic for main monthly view (not compact sidebar)
198+ if ( isSecondWeekOver && ! isCompact ) {
199+ const startDay = 8 ;
200+ const pad = getPadding ( startDay ) ;
201+ days = Array ( pad ) . fill ( null ) ;
202+
203+ for ( let day = startDay ; day <= totalDays ; day ++ ) {
204+ days . push ( browsingDate . set ( "date" , day ) ) ;
205+ }
206+
207+ const remainingInRow = days . length % 7 ;
208+ const extraDays = ( remainingInRow > 0 ? 7 - remainingInRow : 0 ) + 7 ;
209+ const nextMonth = browsingDate . add ( 1 , "month" ) ;
210+
211+ // Add days starting from day 1 of next month
212+ for ( let i = 0 ; i < extraDays ; i ++ ) {
213+ days . push ( nextMonth . set ( "date" , 1 + i ) ) ;
214+ }
215+ } else {
216+ // Traditional calendar grid logic for compact sidebar or early in month
217+ const pad = getPadding ( 1 ) ;
218+ days = Array ( pad ) . fill ( null ) ;
219+
220+ for ( let day = 1 ; day <= totalDays ; day ++ ) {
221+ days . push ( browsingDate . set ( "date" , day ) ) ;
222+ }
160223 }
161224
162225 const [ selectedDatesAndTimes ] = useBookerStore ( ( state ) => [ state . selectedDatesAndTimes ] , shallow ) ;
@@ -188,20 +251,29 @@ const Days = ({
188251
189252 const daysToRenderForTheMonth = days . map ( ( day ) => {
190253 if ( ! day ) return { day : null , disabled : true } ;
254+
191255 const dateKey = yyyymmdd ( day ) ;
192- const oooInfo = slots && slots ?. [ dateKey ] ? slots ?. [ dateKey ] ?. find ( ( slot ) => slot . away ) : null ;
256+ const daySlots = slots ?. [ dateKey ] || [ ] ;
257+ const oooInfo = daySlots . find ( ( slot ) => slot . away ) || null ;
258+
259+ const isNextMonth = day . month ( ) !== browsingDate . month ( ) ;
260+ const isFirstDayOfNextMonth = isSecondWeekOver && ! isCompact && isNextMonth && day . date ( ) === 1 ;
261+
193262 const included = includedDates ?. includes ( dateKey ) ;
194263 const excluded = excludedDates . includes ( dateKey ) ;
195264
196- const isOOOAllDay = ! ! ( slots && slots [ dateKey ] && slots [ dateKey ] . every ( ( slot ) => slot . away ) ) ;
265+ const hasAvailableSlots = daySlots . some ( ( slot ) => ! slot . away ) ;
266+ const isOOOAllDay = daySlots . length > 0 && daySlots . every ( ( slot ) => slot . away ) ;
197267 const away = isOOOAllDay ;
198- const disabled = away ? ! oooInfo ?. toUser : ! included || excluded ;
268+
269+ const disabled = away ? ! oooInfo ?. toUser : isNextMonth ? ! hasAvailableSlots : ! included || excluded ;
199270
200271 return {
201- day : day ,
272+ day,
202273 disabled,
203274 away,
204275 emoji : oooInfo ?. emoji ,
276+ isFirstDayOfNextMonth,
205277 } ;
206278 } ) ;
207279
@@ -239,7 +311,7 @@ const Days = ({
239311
240312 return (
241313 < >
242- { daysToRenderForTheMonth . map ( ( { day, disabled, away, emoji } , idx ) => (
314+ { daysToRenderForTheMonth . map ( ( { day, disabled, away, emoji, isFirstDayOfNextMonth } , idx ) => (
243315 < div key = { day === null ? `e-${ idx } ` : `day-${ day . format ( ) } ` } className = "relative w-full pt-[100%]" >
244316 { day === null ? (
245317 < div key = { `e-${ idx } ` } />
@@ -265,6 +337,8 @@ const Days = ({
265337 active = { isActive ( day ) }
266338 away = { away }
267339 emoji = { emoji }
340+ showMonthTooltip = { isSecondWeekOver && ! isCompact }
341+ isFirstDayOfNextMonth = { isFirstDayOfNextMonth }
268342 />
269343 ) }
270344 </ div >
@@ -297,6 +371,7 @@ const DatePicker = ({
297371 periodDays : null ,
298372 periodType : "UNLIMITED" ,
299373 } ,
374+ isCompact,
300375 ...passThroughProps
301376} : DatePickerProps &
302377 Partial < React . ComponentProps < typeof Days > > & {
@@ -406,6 +481,7 @@ const DatePicker = ({
406481 includedDates = { includedDates }
407482 isBookingInPast = { isBookingInPast }
408483 periodData = { periodData }
484+ isCompact = { isCompact }
409485 />
410486 </ div >
411487 </ div >
0 commit comments