From 6b2783249f8c9ede7f315c91aeeceb162ff75c2d Mon Sep 17 00:00:00 2001 From: benjaminLeongSK Date: Tue, 14 Apr 2026 14:08:43 +0800 Subject: [PATCH 01/10] [#1082][BL] Make time slots tabbable with focus border --- .../time-slot-bar-week-days.style.tsx | 7 +++ .../time-slot-bar-week-days.tsx | 54 +++++++++++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/time-slot-bar-week/time-slot-bar-week-days.style.tsx b/src/time-slot-bar-week/time-slot-bar-week-days.style.tsx index 4296431f09..dccc3654a7 100644 --- a/src/time-slot-bar-week/time-slot-bar-week-days.style.tsx +++ b/src/time-slot-bar-week/time-slot-bar-week-days.style.tsx @@ -130,6 +130,7 @@ export const TimeSlotComponent = styled(TimeSlot)` ${(props) => { if (props.$type === "vertical") { return css` + position: relative; max-width: 200px; height: ${props.$height}px; min-height: ${props.$height}px; @@ -139,6 +140,12 @@ export const TimeSlotComponent = styled(TimeSlot)` } }} + &:focus-within { + outline: 2px solid ${Colour["focus-ring"]}; + outline-offset: 1px; + z-index: 1; + } + ${(props) => { if (!props.$halfFill) { return css` diff --git a/src/time-slot-bar-week/time-slot-bar-week-days.tsx b/src/time-slot-bar-week/time-slot-bar-week-days.tsx index dec6e356d7..42ef6147d7 100644 --- a/src/time-slot-bar-week/time-slot-bar-week-days.tsx +++ b/src/time-slot-bar-week/time-slot-bar-week-days.tsx @@ -5,12 +5,15 @@ import maxBy from "lodash/maxBy"; import minBy from "lodash/minBy"; import React, { useMemo, useState } from "react"; import { useResizeDetector } from "react-resize-detector"; +import { VisuallyHidden } from "../shared/accessibility"; import { InternalCalendarProps } from "../shared/internal-calendar"; import { CellStyleProps, DayCell } from "../shared/internal-calendar/day-cell"; import { Colour } from "../theme"; import { TimeSlot } from "../time-slot-bar/types"; import { DateHelper } from "../util"; import { CalendarHelper } from "../util/calendar-helper"; +import { StringHelper } from "../util/string-helper"; +import { TimeHelper } from "../util/time-helper"; import { CellWeekText, ChevronIcon, @@ -51,6 +54,7 @@ interface TimeSlotWeekDaysProps interface TimeSlotCell extends TimeSlot { cellLength: number; halfFill?: "top" | "bottom" | undefined; + isActualSlot?: boolean | undefined; } export const TimeSlotBarWeekDays = ({ @@ -191,13 +195,16 @@ export const TimeSlotBarWeekDays = ({ function generateFallbackCell( prefix: string | number, - cellLength = 1 + cellLength = 1, + startTime = "", + endTime = "" ): TimeSlotCell { return { id: `${prefix.toString()}-${new Date().getTime()}`, - startTime: "", - endTime: "", + startTime, + endTime, clickable: false, + isActualSlot: false, styleAttributes: { backgroundColor: Colour["bg-stronger"], }, @@ -205,6 +212,20 @@ export const TimeSlotBarWeekDays = ({ }; } + function getSlotAriaLabel(date: string, slot: TimeSlot) { + const { startTime: slotStartTime, endTime: slotEndTime } = slot; + const isAvailable = slot.clickable ?? true; + + return StringHelper.joinNonEmptyStrings([ + dayjs(date).format("D MMMM YYYY dddd"), + slotStartTime && slotEndTime + ? TimeHelper.formatTimeRange(slotStartTime, slotEndTime) + : undefined, + isAvailable ? "Available" : "Unavailable", + slot.label, + ]); + } + function initializeAndFillSlots(slots: TimeSlot[]): TimeSlotCell[] { const cellsArray = Array(numberOfCells).fill({}); @@ -226,6 +247,7 @@ export const TimeSlotBarWeekDays = ({ // Keep fixed slots as 1 long cell cellsArray[Math.floor(startIndex)] = { ...slot, + isActualSlot: true, cellLength: endIndex - startIndex, }; break; @@ -257,6 +279,7 @@ export const TimeSlotBarWeekDays = ({ id: `${slot.id}-${i}`, startTime, endTime, + isActualSlot: true, cellLength: 1, halfFill, }; @@ -442,6 +465,7 @@ export const TimeSlotBarWeekDays = ({ const { id, clickable = true, + isActualSlot, styleAttributes, cellLength, halfFill, @@ -474,7 +498,29 @@ export const TimeSlotBarWeekDays = ({ slot ) } - > + > + {isActualSlot && ( + +