From aebfd4919bd5b1062cd8b58ad184dacbeaf19a4a Mon Sep 17 00:00:00 2001 From: Beto <43630417+betomoedano@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:05:26 -0600 Subject: [PATCH] feat(companion): Update availability screen to use liquid glass (#26041) * Use previous header for android * use activity instead of ternary statements * update layout to use header buttons in availability screen * fix styles and add context menu * add set as default action --------- Co-authored-by: Dhairyashil --- .../app/(tabs)/(availability)/_layout.tsx | 9 + .../index.tsx} | 270 ++++++++---------- companion/app/(tabs)/(event-types)/index.tsx | 3 +- companion/app/(tabs)/_layout.tsx | 2 +- .../AvailabilityListItem.ios.tsx | 118 ++++++++ .../AvailabilityListItem.tsx | 80 ++++++ 6 files changed, 333 insertions(+), 149 deletions(-) create mode 100644 companion/app/(tabs)/(availability)/_layout.tsx rename companion/app/(tabs)/{availability.tsx => (availability)/index.tsx} (74%) create mode 100644 companion/components/availability-list-item/AvailabilityListItem.ios.tsx create mode 100644 companion/components/availability-list-item/AvailabilityListItem.tsx diff --git a/companion/app/(tabs)/(availability)/_layout.tsx b/companion/app/(tabs)/(availability)/_layout.tsx new file mode 100644 index 0000000000..ca52620628 --- /dev/null +++ b/companion/app/(tabs)/(availability)/_layout.tsx @@ -0,0 +1,9 @@ +import { Stack } from "expo-router"; + +export default function EventTypesLayout() { + return ( + + + + ); +} diff --git a/companion/app/(tabs)/availability.tsx b/companion/app/(tabs)/(availability)/index.tsx similarity index 74% rename from companion/app/(tabs)/availability.tsx rename to companion/app/(tabs)/(availability)/index.tsx index 1aa8f86882..462015b486 100644 --- a/companion/app/(tabs)/availability.tsx +++ b/companion/app/(tabs)/(availability)/index.tsx @@ -1,6 +1,6 @@ import { Ionicons } from "@expo/vector-icons"; -import { useRouter } from "expo-router"; -import React, { useState, useMemo } from "react"; +import { Stack, useRouter } from "expo-router"; +import React, { useState, useMemo, Activity } from "react"; import { View, Text, @@ -10,27 +10,27 @@ import { ActionSheetIOS, Alert, Platform, - Modal, TextInput, - KeyboardAvoidingView, ScrollView, } from "react-native"; -import { CalComAPIService, Schedule } from "../../services/calcom"; -import { Header } from "../../components/Header"; -import { FullScreenModal } from "../../components/FullScreenModal"; -import { LoadingSpinner } from "../../components/LoadingSpinner"; -import { EmptyScreen } from "../../components/EmptyScreen"; -import { showErrorAlert } from "../../utils/alerts"; -import { offlineAwareRefresh } from "../../utils/network"; -import { shadows } from "../../utils/shadows"; +import { CalComAPIService, Schedule } from "../../../services/calcom"; +import { Header } from "../../../components/Header"; +import { FullScreenModal } from "../../../components/FullScreenModal"; +import { LoadingSpinner } from "../../../components/LoadingSpinner"; +import { EmptyScreen } from "../../../components/EmptyScreen"; +import { showErrorAlert } from "../../../utils/alerts"; +import { offlineAwareRefresh } from "../../../utils/network"; +import { shadows } from "../../../utils/shadows"; import { useSchedules, useCreateSchedule, useDeleteSchedule, useDuplicateSchedule, useSetScheduleAsDefault, -} from "../../hooks"; +} from "../../../hooks"; +import { isLiquidGlassAvailable } from "expo-glass-effect"; +import { AvailabilityListItem } from "../../../components/availability-list-item/AvailabilityListItem"; export default function Availability() { const router = useRouter(); @@ -261,65 +261,6 @@ export default function Availability() { ); }; - const renderSchedule = ({ item: schedule, index }: { item: Schedule; index: number }) => { - return ( - handleSchedulePress(schedule)} - onLongPress={() => handleScheduleLongPress(schedule)} - style={{ paddingHorizontal: 16, paddingVertical: 16 }} - > - - - - {schedule.name} - {schedule.isDefault ? ( - - Default - - ) : null} - - - {schedule.availability && schedule.availability.length > 0 ? ( - - {schedule.availability.map((slot, slotIndex) => ( - 0 ? "mt-2" : ""} - > - - {slot.days.join(", ")} {slot.startTime} - {slot.endTime} - - - ))} - - ) : ( - No availability set - )} - - - - {schedule.timeZone} - - - - {/* Three dots button - vertically centered on the right */} - { - e.stopPropagation(); - setSelectedSchedule(schedule); - setShowActionsModal(true); - }} - > - - - - - ); - }; - if (loading) { return ( @@ -356,93 +297,126 @@ export default function Availability() { const showList = !showEmptyState && !showSearchEmptyState; return ( - -
+ <> + + + +
+ + + + + New + + + {/* Empty state - no schedules */} - {showEmptyState ? ( - + + } + contentInsetAdjustmentBehavior="automatic" + > + + + + + {/* Search bar and content for non-empty states */} + + {/* Search empty state */} + } > - - ) : null} + - {/* Search bar and content for non-empty states */} - {!showEmptyState ? ( - <> - - - - - New - - - - {/* Search empty state */} - {showSearchEmptyState ? ( - - } - > - - - - ) : null} - - {/* Schedules list */} - {showList ? ( - - - item.id.toString()} - renderItem={renderSchedule} - contentContainerStyle={{ paddingBottom: 90 }} - refreshControl={} - showsVerticalScrollIndicator={false} - /> - - - ) : null} - - ) : null} + {/* Schedules list */} + + item.id.toString()} + renderItem={({ item, index }) => ( + + )} + refreshControl={} + showsVerticalScrollIndicator={false} + contentInsetAdjustmentBehavior="automatic" + /> + + {/* Create Schedule Modal */} {/* Set as Default - only show if not already default */} - {selectedSchedule && !selectedSchedule.isDefault ? ( + <> { @@ -553,7 +529,7 @@ export default function Availability() { - ) : null} + {/* Duplicate */} - + ); } diff --git a/companion/app/(tabs)/(event-types)/index.tsx b/companion/app/(tabs)/(event-types)/index.tsx index 941205d88d..5a82fdea97 100644 --- a/companion/app/(tabs)/(event-types)/index.tsx +++ b/companion/app/(tabs)/(event-types)/index.tsx @@ -473,6 +473,7 @@ export default function EventTypes() { - +
Bookings - + } diff --git a/companion/components/availability-list-item/AvailabilityListItem.ios.tsx b/companion/components/availability-list-item/AvailabilityListItem.ios.tsx new file mode 100644 index 0000000000..907af3afd1 --- /dev/null +++ b/companion/components/availability-list-item/AvailabilityListItem.ios.tsx @@ -0,0 +1,118 @@ +import { Ionicons } from "@expo/vector-icons"; +import { Pressable, Text, View } from "react-native"; +import { AvailabilityListItemProps } from "./AvailabilityListItem"; +import { Host, ContextMenu, Button, Image, HStack } from "@expo/ui/swift-ui"; +import { isLiquidGlassAvailable } from "expo-glass-effect"; +import { buttonStyle, frame, padding } from "@expo/ui/swift-ui/modifiers"; + +export const AvailabilityListItem = ({ + item: schedule, + handleSchedulePress, + onDuplicate, + onDelete, + onSetAsDefault, +}: AvailabilityListItemProps) => { + const scheduleActions: { + label: string; + icon: any; + onPress: () => void; + role: "default" | "destructive"; + }[] = [ + ...(!schedule.isDefault && onSetAsDefault + ? [ + { + label: "Set as Default", + icon: "star", + onPress: () => onSetAsDefault(schedule), + role: "default" as const, + }, + ] + : []), + { + label: "Duplicate", + icon: "square.on.square", + onPress: () => onDuplicate?.(schedule), + role: "default", + }, + { + label: "Delete", + icon: "trash", + onPress: () => onDelete?.(schedule), + role: "destructive", + }, + ]; + + return ( + + + handleSchedulePress(schedule)} className="flex-grow"> + + + {schedule.name} + {schedule.isDefault && ( + + Default + + )} + + + {schedule.availability && schedule.availability.length > 0 ? ( + + {schedule.availability.map((slot, slotIndex) => ( + 0 ? "mt-2" : ""} + > + + {slot.days.join(", ")} {slot.startTime} - {slot.endTime} + + + ))} + + ) : ( + No availability set + )} + + + + {schedule.timeZone} + + + + + {/* Three dots button - vertically centered on the right */} + + + + {scheduleActions.map((scheduleAction) => ( +