Skip to content

Commit 0e6bf54

Browse files
dhairyashiildevin-ai-integration[bot]PeerRichcubic-dev-ai[bot]
authored
feat(companion): dark mode (calcom#27305)
* feat(companion): add dark mode support using NativeWind system preference - Set userInterfaceStyle to 'automatic' in app.json to follow system preference - Add useColorScheme hook from NativeWind to detect color scheme - Update root layout with dynamic colors for StatusBar, containers, and modals - Update tabs layout with dynamic colors for tab bar and icons - Update all Stack.Screen options to use dynamic background colors and blur effects - Support both iOS and Android with appropriate dark mode colors Co-Authored-By: peer@cal.com <peer@cal.com> * fix(companion): use NativeWind dark: variant classes for automatic dark mode Use dark: Tailwind variant classes instead of conditional logic based on useColorScheme for the container background. This ensures NativeWind automatically applies dark mode styles based on system preference. Co-Authored-By: peer@cal.com <peer@cal.com> * fix(companion): use darkMode 'media' for automatic dark mode detection - Changed darkMode from 'class' to 'media' in tailwind.config.js - Updated global.css to use @media (prefers-color-scheme: dark) instead of .dark selector - This ensures NativeWind properly detects system color scheme on iOS/Android Co-Authored-By: peer@cal.com <peer@cal.com> * dark mode starting * feat(companion): add dark mode support to core infrastructure (Phase 1) * feat(companion): add dark mode support to tab pages (Phase 2) * fix(companion): add dark mode support to booking list components for font visibility * fix(companion): add dark mode support to EventTypeListItem components * event types and bookings page * Availability list page * more page, and some ui fixes * feat(companion): add dark mode support to detail screens and modals - BookingDetailScreen (iOS & Android): dynamic colors for all UI elements - AvailabilityDetailScreen (iOS & Android): dark mode backgrounds and text - profile-sheet (iOS & Android): dark mode support for profile modal - RescheduleScreen (all platforms): dark mode for date/time pickers - EditLocationScreen (iOS & Android): dark mode for location editor - AddGuestsScreen: partial dark mode implementation (in progress) Uses iOS system colors: #000000 (black), #1C1C1E (secondary), #FFFFFF (white), #38383A (border), #8E8E93 (text secondary) * fix typecheck and lint * feat(companion): add dark mode support to remaining screens - ScreenWrapper: Fix hardcoded #800020 color with dynamic destructive color - EditAvailabilityDayScreen: Add dark mode for Android/Web and iOS - EditAvailabilityHoursScreen: Add dark mode for Android/Web and iOS - EditAvailabilityNameScreen: Add dark mode for Android/Web and iOS - EditAvailabilityOverrideScreen: Add dark mode for Web, iOS, and Android - MarkNoShowScreen: Add dark mode with dynamic colors for no-show states - MeetingSessionDetailsScreen: Add dark mode for session details - ViewRecordingsScreen: Add dark mode for recordings list Uses iOS system colors (#000000, #1C1C1E, #38383A, #8E8E93) and useColorScheme hook for automatic theme detection. * fix(companion): fix iOS modal dark mode backgrounds for transparent glass UI * fix(companion): fix dark mode for booking action screens in transparent background mode * fix(companion): fix Android dark mode for transparent background in EditAvailabilityOverrideScreen * refactor(companion): centralize dark mode colors and align with main website - Update tailwind.config.js with centralized color definitions - Create constants/colors.ts for inline style color references - Replace iOS system colors with main website neutral grays: - #1C1C1E -> #171717 (secondary dark background) - #38383A -> #4D4D4D (dark border) - #8E8E93 -> #A3A3A3 (secondary text dark) - Keep pure black (#000000) for main backgrounds (OLED friendly) - Add semantic color mappings for consistent UI styling * feat(companion): complete dark mode implementation across all priority files - Priority 1: Updated SettingsUI.tsx, BasicsTab.tsx, LimitsTab.tsx, AdvancedTab.tsx, RecurringTab.tsx, AvailabilityTab.tsx, event-type-detail.tsx - Priority 2: Updated edit-availability-hours/name/override/day.tsx, oauth/callback.tsx - Priority 3: Updated AvailabilityListScreen.tsx with dark mode for modals and list items - Priority 4: Updated BookingActionsModal.tsx, BookingActionsModal.ios.tsx, GlassModalHeader.tsx, LoginScreen.tsx, LocationsList.tsx, SearchHeader.tsx, toast.tsx Dark mode colors used: - Main bg: #000000 (cal.bg.dark) - Secondary bg: #171717 (cal.bg.secondaryDark) - Muted bg: #262626 (cal.bg.mutedDark) - Borders: #4D4D4D (cal.border.dark) - Secondary text: #A3A3A3 (cal.text.secondaryDark) * fix(companion): add dark mode support to iOS header, GlassView modals, and main content background * fix(companion): add dark mode support to toggle/switch components * fix(companion): use iOS green (#34C759) for toggle ON state in dark mode * fix(companion): use white track + black thumb for dark mode toggles * fix(companion): add dark mode support to login page logo and button * dark mode to many pages, centralize the colors * dark mode skelaton * Availability pages android * toggles * headers * red color for dark mode * copy button * white flash on bottom ui native ios sheet * bottom sheet buttons * bottom sheet in gray color * bottom sheet again.. yeahh * toggle green * toggle green * fix lint and typechecks * fix exntension error * Update companion/components/event-type-detail/tabs/LimitsTabDatePicker.android.tsx Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * address cubic comments --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: peer@cal.com <peer@cal.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
1 parent 39f97e7 commit 0e6bf54

101 files changed

Lines changed: 5757 additions & 1997 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

companion/app.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
"name": "Calcom",
44
"slug": "calcom-companion",
55
"scheme": "expo-wxt-app",
6-
"version": "1.0.2",
6+
"version": "1.0.3",
77
"orientation": "portrait",
88
"icon": "./assets/icon.png",
9-
"userInterfaceStyle": "light",
9+
"userInterfaceStyle": "automatic",
1010
"newArchEnabled": true,
1111
"splash": {
1212
"image": "./assets/splash-icon.png",
@@ -17,6 +17,7 @@
1717
"supportsTablet": false,
1818
"deploymentTarget": "18.0",
1919
"bundleIdentifier": "com.cal.companion",
20+
"userInterfaceStyle": "automatic",
2021
"infoPlist": {
2122
"ITSAppUsesNonExemptEncryption": false
2223
},
@@ -33,7 +34,8 @@
3334
"edgeToEdgeEnabled": true,
3435
"package": "com.calcom.companion",
3536
"softwareKeyboardLayoutMode": "pan",
36-
"usesCleartextTraffic": false
37+
"usesCleartextTraffic": false,
38+
"userInterfaceStyle": "automatic"
3739
},
3840
"web": {
3941
"favicon": "./assets/favicon.png",

companion/app/(tabs)/(availability)/availability-detail.ios.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { useColorScheme } from "react-native";
12
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
23
import { useCallback, useRef } from "react";
34
import {
45
AvailabilityDetailScreen,
56
type AvailabilityDetailScreenHandle,
67
} from "@/components/screens/AvailabilityDetailScreen";
8+
import { getColors } from "@/constants/colors";
79

810
// Type for action handlers exposed by AvailabilityDetailScreen.ios.tsx
911
type ActionHandlers = {
@@ -14,6 +16,9 @@ type ActionHandlers = {
1416
export default function AvailabilityDetailIOS() {
1517
const { id } = useLocalSearchParams<{ id: string }>();
1618
const router = useRouter();
19+
const colorScheme = useColorScheme();
20+
const isDark = colorScheme === "dark";
21+
const theme = getColors(isDark);
1722

1823
// Ref to store action handlers from AvailabilityDetailScreen
1924
const actionHandlersRef = useRef<ActionHandlers | null>(null);
@@ -67,13 +72,18 @@ export default function AvailabilityDetailIOS() {
6772
headerBackButtonDisplayMode: "default",
6873
headerTitle: "",
6974
headerStyle: {
70-
backgroundColor: "#f2f2f7",
75+
backgroundColor: isDark ? theme.background : theme.backgroundMuted,
7176
},
7277
headerShadowVisible: false,
7378
}}
7479
/>
7580

76-
<Stack.Header style={{ shadowColor: "transparent", backgroundColor: "#f2f2f7" }}>
81+
<Stack.Header
82+
style={{
83+
shadowColor: "transparent",
84+
backgroundColor: isDark ? theme.background : theme.backgroundMuted,
85+
}}
86+
>
7787
<Stack.Header.Right>
7888
{/* Edit Menu */}
7989
<Stack.Header.Menu>

companion/app/(tabs)/(availability)/availability-detail.tsx

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Ionicons } from "@expo/vector-icons";
22
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
33
import { useCallback, useRef } from "react";
4-
import { Text, TouchableOpacity } from "react-native";
4+
import { Text, useColorScheme } from "react-native";
55
import {
66
AvailabilityDetailScreen,
77
type AvailabilityDetailScreenHandle,
@@ -14,6 +14,8 @@ import {
1414
DropdownMenuSeparator,
1515
DropdownMenuTrigger,
1616
} from "@/components/ui/dropdown-menu";
17+
import { getColors } from "@/constants/colors";
18+
import { AppPressable } from "@/components/AppPressable";
1719

1820
// Type for action handlers exposed by AvailabilityDetailScreen
1921
type ActionHandlers = {
@@ -24,6 +26,9 @@ type ActionHandlers = {
2426
export default function AvailabilityDetail() {
2527
const { id } = useLocalSearchParams<{ id: string }>();
2628
const router = useRouter();
29+
const colorScheme = useColorScheme();
30+
const isDark = colorScheme === "dark";
31+
const theme = getColors(isDark);
2732

2833
// Ref to store action handlers from AvailabilityDetailScreen
2934
const actionHandlersRef = useRef<ActionHandlers | null>(null);
@@ -72,47 +77,82 @@ export default function AvailabilityDetail() {
7277
<>
7378
<Stack.Screen
7479
options={{
75-
title: "Availability",
76-
headerBackTitle: "Availability",
80+
title: "",
81+
headerBackTitle: "", // Hide default back title if it appears
7782
headerStyle: {
78-
backgroundColor: "#f2f2f7",
83+
backgroundColor: isDark ? "black" : "white",
7984
},
8085
headerShadowVisible: false,
86+
headerLeft: () => (
87+
<HeaderButtonWrapper side="left">
88+
<AppPressable
89+
onPress={() => router.back()}
90+
className="mr-2 h-10 flex-row items-center justify-center rounded-full border border-[#E5E5E5] bg-white px-3 dark:border-[#262626] dark:bg-[#171717]"
91+
>
92+
<Ionicons
93+
name="chevron-back"
94+
size={20}
95+
color={isDark ? "#FFFFFF" : "#000000"}
96+
style={{ marginRight: 4 }}
97+
/>
98+
<Text className={`text-[15px] font-medium ${isDark ? "text-white" : "text-black"}`}>
99+
Availability
100+
</Text>
101+
</AppPressable>
102+
</HeaderButtonWrapper>
103+
),
81104
headerRight: () => (
82105
<HeaderButtonWrapper side="right">
83106
<DropdownMenu>
84107
<DropdownMenuTrigger asChild>
85-
<TouchableOpacity
86-
style={{
87-
padding: 8,
88-
marginRight: -8,
89-
}}
90-
>
91-
<Ionicons name="ellipsis-horizontal-circle" size={24} color="#000000" />
92-
</TouchableOpacity>
108+
<AppPressable className="h-10 flex-row items-center justify-center rounded-full border border-[#E5E5E5] bg-white px-4 dark:border-[#262626] dark:bg-[#171717]">
109+
<Text
110+
className={`text-[15px] font-medium ${isDark ? "text-white" : "text-black"}`}
111+
>
112+
Edit
113+
</Text>
114+
</AppPressable>
93115
</DropdownMenuTrigger>
94116
<DropdownMenuContent align="end" className="w-56">
95117
<DropdownMenuItem onPress={handleEditNameAndTimezone}>
96-
<Ionicons name="pencil-outline" size={18} color="#000" />
97-
<Text className="ml-2 text-[15px] text-black">Name and Timezone</Text>
118+
<Ionicons
119+
name="pencil-outline"
120+
size={18}
121+
color={isDark ? theme.text : "#000"}
122+
/>
123+
<Text className="ml-2 text-[15px] text-black dark:text-white">
124+
Name and Timezone
125+
</Text>
98126
</DropdownMenuItem>
99127
<DropdownMenuItem onPress={handleEditWorkingHours}>
100-
<Ionicons name="time-outline" size={18} color="#000" />
101-
<Text className="ml-2 text-[15px] text-black">Working Hours</Text>
128+
<Ionicons name="time-outline" size={18} color={isDark ? theme.text : "#000"} />
129+
<Text className="ml-2 text-[15px] text-black dark:text-white">
130+
Working Hours
131+
</Text>
102132
</DropdownMenuItem>
103133
<DropdownMenuItem onPress={handleEditOverride}>
104-
<Ionicons name="calendar-outline" size={18} color="#000" />
105-
<Text className="ml-2 text-[15px] text-black">Date Override</Text>
134+
<Ionicons
135+
name="calendar-outline"
136+
size={18}
137+
color={isDark ? theme.text : "#000"}
138+
/>
139+
<Text className="ml-2 text-[15px] text-black dark:text-white">
140+
Date Override
141+
</Text>
106142
</DropdownMenuItem>
107143
<DropdownMenuSeparator />
108144
<DropdownMenuItem onPress={handleSetAsDefault}>
109-
<Ionicons name="star-outline" size={18} color="#000" />
110-
<Text className="ml-2 text-[15px] text-black">Set as Default</Text>
145+
<Ionicons name="star-outline" size={18} color={isDark ? theme.text : "#000"} />
146+
<Text className="ml-2 text-[15px] text-black dark:text-white">
147+
Set as Default
148+
</Text>
111149
</DropdownMenuItem>
112150
<DropdownMenuSeparator />
113151
<DropdownMenuItem onPress={handleDelete} variant="destructive">
114-
<Ionicons name="trash-outline" size={18} color="#FF3B30" />
115-
<Text className="ml-2 text-[15px] text-[#FF3B30]">Delete Schedule</Text>
152+
<Ionicons name="trash-outline" size={18} color={theme.error} />
153+
<Text className="ml-2 text-[15px]" style={{ color: theme.error }}>
154+
Delete Schedule
155+
</Text>
116156
</DropdownMenuItem>
117157
</DropdownMenuContent>
118158
</DropdownMenu>

companion/app/(tabs)/(availability)/index.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { isLiquidGlassAvailable } from "expo-glass-effect";
22
import { Image } from "expo-image";
33
import { Stack, useRouter } from "expo-router";
44
import { useState } from "react";
5-
import { Alert, Platform, Pressable } from "react-native";
5+
import { Alert, Platform, Pressable, useColorScheme } from "react-native";
66
import { AvailabilityListScreen } from "@/components/screens/AvailabilityListScreen";
77
import { useCreateSchedule, useUserProfile } from "@/hooks";
88
import { CalComAPIService } from "@/services/calcom";
@@ -15,6 +15,8 @@ export default function Availability() {
1515
const [showCreateModal, setShowCreateModal] = useState(false);
1616
const { mutate: createScheduleMutation } = useCreateSchedule();
1717
const { data: userProfile } = useUserProfile();
18+
const colorScheme = useColorScheme();
19+
const isDark = colorScheme === "dark";
1820

1921
const handleCreateNew = () => {
2022
// Use native iOS Alert.prompt for a native look
@@ -93,7 +95,7 @@ export default function Availability() {
9395
<>
9496
<Stack.Header
9597
style={{ backgroundColor: "transparent", shadowColor: "transparent" }}
96-
blurEffect={isLiquidGlassAvailable() ? undefined : "light"}
98+
blurEffect={isLiquidGlassAvailable() ? undefined : isDark ? "dark" : "light"}
9799
hidden={Platform.OS === "android" || Platform.OS === "web"}
98100
>
99101
<Stack.Header.Title large>Availability</Stack.Header.Title>
@@ -127,7 +129,7 @@ export default function Availability() {
127129
placeholder="Search schedules"
128130
onChangeText={(e) => setSearchQuery(e.nativeEvent.text)}
129131
obscureBackground={false}
130-
barTintColor="#fff"
132+
barTintColor={isDark ? "#000" : "#fff"}
131133
/>
132134
</Stack.Header>
133135

companion/app/(tabs)/(bookings)/booking-detail.ios.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as Clipboard from "expo-clipboard";
22
import { Stack, useLocalSearchParams } from "expo-router";
33
import { useCallback, useMemo, useRef } from "react";
4+
import { useColorScheme } from "react-native";
45
import { BookingDetailScreen } from "@/components/screens/BookingDetailScreen";
56
import { useAuth } from "@/contexts/AuthContext";
67
import { useBookingByUid } from "@/hooks/useBookings";
@@ -63,6 +64,7 @@ const getMeetingUrl = (booking: Booking | null): string | null => {
6364
export default function BookingDetailIOS() {
6465
const { uid } = useLocalSearchParams<{ uid: string }>();
6566
const { userInfo } = useAuth();
67+
const colorScheme = useColorScheme();
6668

6769
// Use React Query hook for booking data - single source of truth
6870
const { data: booking, isLoading, error, refetch, isRefetching } = useBookingByUid(uid);
@@ -295,7 +297,11 @@ export default function BookingDetailIOS() {
295297
</Stack.Header.Menu>
296298

297299
{meetingUrl && (
298-
<Stack.Header.Button onPress={handleJoinMeeting} variant="prominent" tintColor="#000">
300+
<Stack.Header.Button
301+
onPress={handleJoinMeeting}
302+
variant="prominent"
303+
tintColor={colorScheme === "dark" ? "#FFF" : "#000"}
304+
>
299305
Join
300306
</Stack.Header.Button>
301307
)}

0 commit comments

Comments
 (0)