From 7a27f4962b00a30e43ea39410e6bb5af7f895834 Mon Sep 17 00:00:00 2001 From: Benoit Pingris Date: Tue, 9 Apr 2024 18:38:16 +0200 Subject: [PATCH 1/5] wip: configure darkmode with props --- .../src/components/Calendar.tsx | 32 +++++++++++++++++-- packages/flash-calendar/src/hooks/useTheme.ts | 11 +++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/packages/flash-calendar/src/components/Calendar.tsx b/packages/flash-calendar/src/components/Calendar.tsx index 3f98e81..4aed42f 100644 --- a/packages/flash-calendar/src/components/Calendar.tsx +++ b/packages/flash-calendar/src/components/Calendar.tsx @@ -1,4 +1,4 @@ -import { memo, useEffect } from "react"; +import { memo, createContext, useEffect, useContext } from "react"; import type { CalendarItemDayContainerProps, @@ -68,8 +68,25 @@ export interface CalendarProps extends UseCalendarParams { calendarMonthHeaderHeight?: number; /** Theme to customize the calendar component. */ theme?: CalendarTheme; + /** Enable dark mode */ + darkMode?: boolean; } +const calendarContext = createContext<{ darkMode?: boolean } | undefined>( + undefined +); + +export const useCalendarContext = () => { + const context = useContext(calendarContext); + + if (!context) { + throw new Error( + "useCalendarContext must be called inside " + ); + } + return context; +}; + const BaseCalendar = memo( ({ onCalendarDayPress, @@ -150,7 +167,12 @@ const BaseCalendar = memo( BaseCalendar.displayName = "BaseCalendar"; export const Calendar = memo( - ({ calendarActiveDateRanges, calendarMonthId, ...props }: CalendarProps) => { + ({ + calendarActiveDateRanges, + calendarMonthId, + darkMode, + ...props + }: CalendarProps) => { useEffect(() => { activeDateRangesEmitter.emit( "onSetActiveDateRanges", @@ -168,7 +190,11 @@ export const Calendar = memo( */ }, [calendarActiveDateRanges, calendarMonthId]); - return ; + return ( + + + + ); } ); diff --git a/packages/flash-calendar/src/hooks/useTheme.ts b/packages/flash-calendar/src/hooks/useTheme.ts index 13c0b60..25901a7 100644 --- a/packages/flash-calendar/src/hooks/useTheme.ts +++ b/packages/flash-calendar/src/hooks/useTheme.ts @@ -2,8 +2,19 @@ import { useColorScheme } from "react-native"; import type { BaseTheme } from "@/helpers/tokens"; import { darkTheme, lightTheme } from "@/helpers/tokens"; +import { useCalendarContext } from "@/components/Calendar"; export const useTheme = (): BaseTheme => { const appearance = useColorScheme(); + const darkMode = useCalendarContext(); + + if (darkMode) { + return darkTheme; + } + + if (darkMode === false) { + return lightTheme; + } + return appearance === "dark" ? darkTheme : lightTheme; }; From d43810c462527ad19a0380a0b287f22be97ead3e Mon Sep 17 00:00:00 2001 From: Benoit Pingris Date: Thu, 11 Apr 2024 09:48:20 +0200 Subject: [PATCH 2/5] chore: memoize context value + rename files & variablse --- .../src/components/Calendar.stories.tsx | 6 +++ .../src/components/Calendar.tsx | 42 +++++++++---------- .../src/components/CalendarList.tsx | 42 ++++++++++++------- .../src/hooks/useCalendarTheme.ts | 22 ++++++++++ packages/flash-calendar/src/hooks/useTheme.ts | 15 +++---- 5 files changed, 80 insertions(+), 47 deletions(-) create mode 100644 packages/flash-calendar/src/hooks/useCalendarTheme.ts diff --git a/packages/flash-calendar/src/components/Calendar.stories.tsx b/packages/flash-calendar/src/components/Calendar.stories.tsx index fd8a75e..c0d290b 100644 --- a/packages/flash-calendar/src/components/Calendar.stories.tsx +++ b/packages/flash-calendar/src/components/Calendar.stories.tsx @@ -159,3 +159,9 @@ export const DateRangePicker = (args: typeof KichenSink.args) => { /> ); }; + +export const ControlledColorScheme: StoryObj = { + args: { + colorSchemeToOverride: "dark", + }, +}; diff --git a/packages/flash-calendar/src/components/Calendar.tsx b/packages/flash-calendar/src/components/Calendar.tsx index 4aed42f..199b40b 100644 --- a/packages/flash-calendar/src/components/Calendar.tsx +++ b/packages/flash-calendar/src/components/Calendar.tsx @@ -1,4 +1,5 @@ -import { memo, createContext, useEffect, useContext } from "react"; +import { memo, useEffect, useMemo } from "react"; +import type { ColorSchemeName } from "react-native"; import type { CalendarItemDayContainerProps, @@ -22,6 +23,8 @@ import type { BaseTheme } from "@/helpers/tokens"; import type { UseCalendarParams } from "@/hooks/useCalendar"; import { useCalendar } from "@/hooks/useCalendar"; import { activeDateRangesEmitter } from "@/hooks/useOptimizedDayMetadata"; +import type { CalendarThemeContext } from "@/hooks/useCalendarTheme"; +import { calendarThemeContext } from "@/hooks/useCalendarTheme"; export interface CalendarTheme { rowMonth?: CalendarRowMonthProps["theme"]; @@ -68,25 +71,17 @@ export interface CalendarProps extends UseCalendarParams { calendarMonthHeaderHeight?: number; /** Theme to customize the calendar component. */ theme?: CalendarTheme; - /** Enable dark mode */ - darkMode?: boolean; + /** + * When set, Flash Calendar will use this color scheme instead of the system's + * value (`light|dark`). This is useful if your app doesn't support dark-mode, + * for example. + * + * We don't advise using this prop - ideally, your app should reflect the + * user's preferences. + */ + colorSchemeToOverride?: ColorSchemeName; } -const calendarContext = createContext<{ darkMode?: boolean } | undefined>( - undefined -); - -export const useCalendarContext = () => { - const context = useContext(calendarContext); - - if (!context) { - throw new Error( - "useCalendarContext must be called inside " - ); - } - return context; -}; - const BaseCalendar = memo( ({ onCalendarDayPress, @@ -170,7 +165,7 @@ export const Calendar = memo( ({ calendarActiveDateRanges, calendarMonthId, - darkMode, + colorSchemeToOverride, ...props }: CalendarProps) => { useEffect(() => { @@ -190,10 +185,15 @@ export const Calendar = memo( */ }, [calendarActiveDateRanges, calendarMonthId]); + const calendarThemeContextValue = useMemo( + () => ({ colorScheme: colorSchemeToOverride }), + [colorSchemeToOverride] + ); + return ( - + - + ); } ); diff --git a/packages/flash-calendar/src/components/CalendarList.tsx b/packages/flash-calendar/src/components/CalendarList.tsx index df61560..3ea925c 100644 --- a/packages/flash-calendar/src/components/CalendarList.tsx +++ b/packages/flash-calendar/src/components/CalendarList.tsx @@ -16,6 +16,8 @@ import { Calendar } from "@/components/Calendar"; import { startOfMonth, toDateId } from "@/helpers/dates"; import type { CalendarMonth } from "@/hooks/useCalendarList"; import { getHeightForMonth, useCalendarList } from "@/hooks/useCalendarList"; +import type { CalendarThemeContext } from "@/hooks/useCalendarTheme"; +import { calendarThemeContext } from "@/hooks/useCalendarTheme"; /** * Represents each `CalendarList` item. It's enhanced with the required @@ -118,6 +120,7 @@ export const CalendarList = memo( // Other props theme, + colorSchemeToOverride, onEndReached, ...props }: CalendarListProps, @@ -266,23 +269,30 @@ export const CalendarList = memo( return { paddingBottom: calendarSpacing }; }, [calendarSpacing]); + const calendarThemeContextValue = useMemo( + () => ({ colorScheme: colorSchemeToOverride }), + [colorSchemeToOverride] + ); + return ( - ( - - - - )} - showsVerticalScrollIndicator={false} - {...flatListProps} - /> + + ( + + + + )} + showsVerticalScrollIndicator={false} + {...flatListProps} + /> + ); } ) diff --git a/packages/flash-calendar/src/hooks/useCalendarTheme.ts b/packages/flash-calendar/src/hooks/useCalendarTheme.ts new file mode 100644 index 0000000..bf9414c --- /dev/null +++ b/packages/flash-calendar/src/hooks/useCalendarTheme.ts @@ -0,0 +1,22 @@ +import { createContext, useContext } from "react"; +import type { ColorSchemeName } from "react-native"; + +export interface CalendarThemeContext { + /** The overridden color scheme */ + colorScheme?: ColorSchemeName; +} + +export const calendarThemeContext = createContext< + CalendarThemeContext | undefined +>(undefined); + +export const useCalendarThemeContext = () => { + const context = useContext(calendarThemeContext); + + if (!context) { + throw new Error( + "useCalendarThemeContext must be called inside " + ); + } + return context; +}; diff --git a/packages/flash-calendar/src/hooks/useTheme.ts b/packages/flash-calendar/src/hooks/useTheme.ts index 25901a7..530121b 100644 --- a/packages/flash-calendar/src/hooks/useTheme.ts +++ b/packages/flash-calendar/src/hooks/useTheme.ts @@ -2,19 +2,14 @@ import { useColorScheme } from "react-native"; import type { BaseTheme } from "@/helpers/tokens"; import { darkTheme, lightTheme } from "@/helpers/tokens"; -import { useCalendarContext } from "@/components/Calendar"; + +import { useCalendarThemeContext } from "./useCalendarTheme"; export const useTheme = (): BaseTheme => { const appearance = useColorScheme(); - const darkMode = useCalendarContext(); - - if (darkMode) { - return darkTheme; - } + const { colorScheme } = useCalendarThemeContext(); - if (darkMode === false) { - return lightTheme; - } + const effectiveTheme = colorScheme ?? appearance; - return appearance === "dark" ? darkTheme : lightTheme; + return effectiveTheme === "dark" ? darkTheme : lightTheme; }; From 430899fa10235870bbde1e8bb77791e8b902a4d5 Mon Sep 17 00:00:00 2001 From: Marcelo T Prado Date: Thu, 18 Apr 2024 09:34:55 -0700 Subject: [PATCH 3/5] Rename prop to `calendarColorScheme` and avoid leaking context --- .../PerfTestCalendar/PerfTestCalendar.tsx | 2 +- .../src/components/Calendar.stories.tsx | 2 +- .../src/components/Calendar.tsx | 23 ++++------ .../src/components/CalendarThemeProvider.tsx | 43 +++++++++++++++++++ .../src/hooks/useCalendarTheme.ts | 22 ---------- packages/flash-calendar/src/hooks/useTheme.ts | 9 ++-- 6 files changed, 58 insertions(+), 43 deletions(-) create mode 100644 packages/flash-calendar/src/components/CalendarThemeProvider.tsx delete mode 100644 packages/flash-calendar/src/hooks/useCalendarTheme.ts diff --git a/apps/example/src/components/PerfTestCalendar/PerfTestCalendar.tsx b/apps/example/src/components/PerfTestCalendar/PerfTestCalendar.tsx index f3a794e..18589a9 100644 --- a/apps/example/src/components/PerfTestCalendar/PerfTestCalendar.tsx +++ b/apps/example/src/components/PerfTestCalendar/PerfTestCalendar.tsx @@ -8,8 +8,8 @@ import { memo, useEffect } from "react"; import { Text } from "react-native"; import { uppercaseFirstLetter } from "@/helpers/strings"; -import { useRenderCount } from "./useRenderCount"; import { PerfTestCalendarItemDayWithContainer } from "./PerfTestCalendarItemDay"; +import { useRenderCount } from "./useRenderCount"; const BasePerfTestCalendar = memo( ({ diff --git a/packages/flash-calendar/src/components/Calendar.stories.tsx b/packages/flash-calendar/src/components/Calendar.stories.tsx index c0d290b..a4f6f57 100644 --- a/packages/flash-calendar/src/components/Calendar.stories.tsx +++ b/packages/flash-calendar/src/components/Calendar.stories.tsx @@ -162,6 +162,6 @@ export const DateRangePicker = (args: typeof KichenSink.args) => { export const ControlledColorScheme: StoryObj = { args: { - colorSchemeToOverride: "dark", + calendarColorScheme: "dark", }, }; diff --git a/packages/flash-calendar/src/components/Calendar.tsx b/packages/flash-calendar/src/components/Calendar.tsx index 199b40b..28a83c8 100644 --- a/packages/flash-calendar/src/components/Calendar.tsx +++ b/packages/flash-calendar/src/components/Calendar.tsx @@ -1,4 +1,4 @@ -import { memo, useEffect, useMemo } from "react"; +import { memo, useEffect } from "react"; import type { ColorSchemeName } from "react-native"; import type { @@ -23,8 +23,7 @@ import type { BaseTheme } from "@/helpers/tokens"; import type { UseCalendarParams } from "@/hooks/useCalendar"; import { useCalendar } from "@/hooks/useCalendar"; import { activeDateRangesEmitter } from "@/hooks/useOptimizedDayMetadata"; -import type { CalendarThemeContext } from "@/hooks/useCalendarTheme"; -import { calendarThemeContext } from "@/hooks/useCalendarTheme"; +import { CalendarThemeProvider } from "@/components/CalendarThemeProvider"; export interface CalendarTheme { rowMonth?: CalendarRowMonthProps["theme"]; @@ -69,8 +68,6 @@ export interface CalendarProps extends UseCalendarParams { * @defaultValue 20 */ calendarMonthHeaderHeight?: number; - /** Theme to customize the calendar component. */ - theme?: CalendarTheme; /** * When set, Flash Calendar will use this color scheme instead of the system's * value (`light|dark`). This is useful if your app doesn't support dark-mode, @@ -78,8 +75,11 @@ export interface CalendarProps extends UseCalendarParams { * * We don't advise using this prop - ideally, your app should reflect the * user's preferences. + * @defaultValue undefined */ - colorSchemeToOverride?: ColorSchemeName; + calendarColorScheme?: ColorSchemeName; + /** Theme to customize the calendar component. */ + theme?: CalendarTheme; } const BaseCalendar = memo( @@ -165,7 +165,7 @@ export const Calendar = memo( ({ calendarActiveDateRanges, calendarMonthId, - colorSchemeToOverride, + calendarColorScheme, ...props }: CalendarProps) => { useEffect(() => { @@ -185,15 +185,10 @@ export const Calendar = memo( */ }, [calendarActiveDateRanges, calendarMonthId]); - const calendarThemeContextValue = useMemo( - () => ({ colorScheme: colorSchemeToOverride }), - [colorSchemeToOverride] - ); - return ( - + - + ); } ); diff --git a/packages/flash-calendar/src/components/CalendarThemeProvider.tsx b/packages/flash-calendar/src/components/CalendarThemeProvider.tsx new file mode 100644 index 0000000..a49bf4d --- /dev/null +++ b/packages/flash-calendar/src/components/CalendarThemeProvider.tsx @@ -0,0 +1,43 @@ +import type { ReactNode } from "react"; +import { createContext, useContext, useMemo } from "react"; +import type { ColorSchemeName } from "react-native"; + +export interface CalendarThemeContextType { + colorScheme?: ColorSchemeName; +} + +const CalendarThemeContext = createContext({ + colorScheme: undefined, +}); + +export const CalendarThemeProvider = ({ + children, + colorScheme, +}: { + children: ReactNode; + /** + * When set, Flash Calendar will use this color scheme instead of the system's + * value (`light|dark`). This is useful if your app doesn't support dark-mode, + * for example. + * + * We don't advise using this prop - ideally, your app should reflect the + * user's preferences. + */ + colorScheme?: ColorSchemeName; +}) => { + const calendarThemeContextValue = useMemo( + () => ({ colorScheme }), + [colorScheme] + ); + + return ( + + {children} + + ); +}; + +export const useCalendarTheme = () => { + const context = useContext(CalendarThemeContext); + return context; +}; diff --git a/packages/flash-calendar/src/hooks/useCalendarTheme.ts b/packages/flash-calendar/src/hooks/useCalendarTheme.ts deleted file mode 100644 index bf9414c..0000000 --- a/packages/flash-calendar/src/hooks/useCalendarTheme.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createContext, useContext } from "react"; -import type { ColorSchemeName } from "react-native"; - -export interface CalendarThemeContext { - /** The overridden color scheme */ - colorScheme?: ColorSchemeName; -} - -export const calendarThemeContext = createContext< - CalendarThemeContext | undefined ->(undefined); - -export const useCalendarThemeContext = () => { - const context = useContext(calendarThemeContext); - - if (!context) { - throw new Error( - "useCalendarThemeContext must be called inside " - ); - } - return context; -}; diff --git a/packages/flash-calendar/src/hooks/useTheme.ts b/packages/flash-calendar/src/hooks/useTheme.ts index 530121b..32552e5 100644 --- a/packages/flash-calendar/src/hooks/useTheme.ts +++ b/packages/flash-calendar/src/hooks/useTheme.ts @@ -2,14 +2,13 @@ import { useColorScheme } from "react-native"; import type { BaseTheme } from "@/helpers/tokens"; import { darkTheme, lightTheme } from "@/helpers/tokens"; - -import { useCalendarThemeContext } from "./useCalendarTheme"; +import { useCalendarTheme } from "@/components/CalendarThemeProvider"; export const useTheme = (): BaseTheme => { const appearance = useColorScheme(); - const { colorScheme } = useCalendarThemeContext(); + const { colorScheme } = useCalendarTheme(); - const effectiveTheme = colorScheme ?? appearance; + const theme = colorScheme ?? appearance; - return effectiveTheme === "dark" ? darkTheme : lightTheme; + return theme === "dark" ? darkTheme : lightTheme; }; From a97867a2458a8d17bad8f40af9a7749ae6769e91 Mon Sep 17 00:00:00 2001 From: Marcelo T Prado Date: Thu, 18 Apr 2024 09:36:55 -0700 Subject: [PATCH 4/5] Forward colorScheme to Calendar instead of using context in CalendarList --- .../src/components/CalendarList.tsx | 55 ++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/packages/flash-calendar/src/components/CalendarList.tsx b/packages/flash-calendar/src/components/CalendarList.tsx index 3ea925c..294f308 100644 --- a/packages/flash-calendar/src/components/CalendarList.tsx +++ b/packages/flash-calendar/src/components/CalendarList.tsx @@ -16,8 +16,6 @@ import { Calendar } from "@/components/Calendar"; import { startOfMonth, toDateId } from "@/helpers/dates"; import type { CalendarMonth } from "@/hooks/useCalendarList"; import { getHeightForMonth, useCalendarList } from "@/hooks/useCalendarList"; -import type { CalendarThemeContext } from "@/hooks/useCalendarTheme"; -import { calendarThemeContext } from "@/hooks/useCalendarTheme"; /** * Represents each `CalendarList` item. It's enhanced with the required @@ -119,8 +117,8 @@ export const CalendarList = memo( calendarAdditionalHeight = 0, // Other props + calendarColorScheme, theme, - colorSchemeToOverride, onEndReached, ...props }: CalendarListProps, @@ -140,20 +138,21 @@ export const CalendarList = memo( const calendarProps = useMemo( (): CalendarMonthEnhanced["calendarProps"] => ({ + calendarColorScheme, calendarActiveDateRanges, calendarDayHeight, + calendarDisabledDateIds, calendarFirstDayOfWeek, - getCalendarDayFormat, - getCalendarWeekDayFormat, + calendarFormatLocale, calendarMaxDateId, calendarMinDateId, - calendarFormatLocale, calendarMonthHeaderHeight, calendarRowHorizontalSpacing, - getCalendarMonthFormat, calendarRowVerticalSpacing, calendarWeekHeaderHeight, - calendarDisabledDateIds, + getCalendarDayFormat, + getCalendarMonthFormat, + getCalendarWeekDayFormat, onCalendarDayPress, theme, }), @@ -172,6 +171,7 @@ export const CalendarList = memo( calendarRowVerticalSpacing, calendarWeekHeaderHeight, calendarDisabledDateIds, + calendarColorScheme, onCalendarDayPress, theme, ] @@ -269,30 +269,23 @@ export const CalendarList = memo( return { paddingBottom: calendarSpacing }; }, [calendarSpacing]); - const calendarThemeContextValue = useMemo( - () => ({ colorScheme: colorSchemeToOverride }), - [colorSchemeToOverride] - ); - return ( - - ( - - - - )} - showsVerticalScrollIndicator={false} - {...flatListProps} - /> - + ( + + + + )} + showsVerticalScrollIndicator={false} + {...flatListProps} + /> ); } ) From dd68c1537a6338f7997464ee7de6e3b55f7f810e Mon Sep 17 00:00:00 2001 From: Marcelo T Prado Date: Thu, 18 Apr 2024 09:46:05 -0700 Subject: [PATCH 5/5] add docs and changeset --- .changeset/eighty-papayas-bathe.md | 5 +++ .../docs/fundamentals/tips-and-tricks.mdx | 34 ++++++++++++++++++- .../src/components/Calendar.stories.tsx | 16 +++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 .changeset/eighty-papayas-bathe.md diff --git a/.changeset/eighty-papayas-bathe.md b/.changeset/eighty-papayas-bathe.md new file mode 100644 index 0000000..36e3298 --- /dev/null +++ b/.changeset/eighty-papayas-bathe.md @@ -0,0 +1,5 @@ +--- +"@marceloterreiro/flash-calendar": patch +--- + +- Add an optional `calendarColorScheme` prop that enables overriding the color scheme used by Flash Calendar. diff --git a/apps/docs/docs/fundamentals/tips-and-tricks.mdx b/apps/docs/docs/fundamentals/tips-and-tricks.mdx index feaefc3..7c41c13 100644 --- a/apps/docs/docs/fundamentals/tips-and-tricks.mdx +++ b/apps/docs/docs/fundamentals/tips-and-tricks.mdx @@ -109,7 +109,9 @@ export function ImperativeScrolling() { ## Setting Border Radius to `Calendar.Item.Day` -To apply a border radius to the `Calendar.Item.Day` component, it's necessary to specify the radius for all four corners. Here's an example of how to achieve this: +To apply a border radius to the `Calendar.Item.Day` component, it's necessary to +specify the radius for all four corners. Here's an example of how to achieve +this: ```tsx itemDay: { @@ -123,3 +125,33 @@ itemDay: { }), } ``` + +## Avoiding dark mode + +If your app doesn't support dynamic themes, you can override Flash Calendar's +color scheme by passing a `calendarColorScheme` prop: + +```tsx +export const LightModeOnly = () => { + const { calendarActiveDateRanges, onCalendarDayPress } = useDateRange({ + startId: "2024-02-04", + endId: "2024-02-09", + }); + + return ( + + ); +}; +``` + +When set, Flash Calendar's theming system will use this scheme instead of the +user system's theme. + +**Note**: you should avoid using this prop. Instead, your app should +support dynamic themes that react to the user's system preferences. The prop is +provided as an escape hatch for apps that doesn't support dynamic themes yet. diff --git a/packages/flash-calendar/src/components/Calendar.stories.tsx b/packages/flash-calendar/src/components/Calendar.stories.tsx index a4f6f57..5328c55 100644 --- a/packages/flash-calendar/src/components/Calendar.stories.tsx +++ b/packages/flash-calendar/src/components/Calendar.stories.tsx @@ -165,3 +165,19 @@ export const ControlledColorScheme: StoryObj = { calendarColorScheme: "dark", }, }; + +export const LightModeOnly = () => { + const { calendarActiveDateRanges, onCalendarDayPress } = useDateRange({ + startId: "2024-02-04", + endId: "2024-02-09", + }); + + return ( + + ); +};